Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): allow to use ssl on server #8722

Merged
merged 14 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/twenty-server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
# LOGIN_TOKEN_EXPIRES_IN=15m
# REFRESH_TOKEN_EXPIRES_IN=90d
# FILE_TOKEN_EXPIRES_IN=1d
# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
# CALENDAR_PROVIDER_GOOGLE_ENABLED=false
# IS_BILLING_ENABLED=false
Expand Down Expand Up @@ -75,3 +74,5 @@ ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
# PG_SSL_ALLOW_SELF_SIGNED=true
# SESSION_STORE_SECRET=replace_me_with_a_random_string_session
# ENTERPRISE_KEY=replace_me_with_a_valid_enterprise_key
# SSL_KEY_PATH="./certs/your-cert.key"
# SSL_CERT_PATH="./certs/your-cert.crt"
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved
74 changes: 74 additions & 0 deletions packages/twenty-server/scripts/ssl-generation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Local SSL Certificate Generation Script

This Bash script helps generate self-signed SSL certificates for local development. It uses OpenSSL to create a root certificate authority, a domain certificate, and configures them for local usage.

## Features
- Generates a private key and root certificate.
- Creates a signed certificate for a specified domain.
- Adds the root certificate to the macOS keychain for trusted usage (macOS only).
- Customizable with default values for easier use.

## Requirements
- OpenSSL

## Usage

### Running the Script

To generate certificates using the default values:

```sh
./script.sh
```

### Specifying Custom Values

1. **Domain Name**: Specify the domain name for the certificate. Default is `localhost.com`.
2. **Root Certificate Name**: Specify a name for the root certificate. Default is `myRootCertificate`.
3. **Validity Days**: Specify the number of days the certificate is valid for. Default is `398` days.

#### Examples:

1. **Using Default Values**:
```sh
./script.sh
```

2. **Custom Domain Name**:
```sh
./script.sh example.com
```

3. **Custom Domain Name and Root Certificate Name**:
```sh
./script.sh example.com customRootCertificate
```

4. **Custom Domain Name, Root Certificate Name, and Validity Days**:
```sh
./script.sh example.com customRootCertificate 398
```

## Script Details

1. **Check if OpenSSL is Installed**: Ensures OpenSSL is installed before executing.
2. **Create Directory for Certificates**: Uses `~/certs/{domain}`.
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved
3. **Generate Root Certificate**: Creates a root private key and certificate.
4. **Add Root Certificate to macOS Keychain**: Adds root certificate to macOS trusted store (requires admin privileges).
5. **Generate Domain Key**: Produces a private key for the domain.
6. **Create CSR**: Generates a Certificate Signing Request for the domain.
7. **Generate Signed Certificate**: Signs the domain certificate with the root certificate.

## Output Files

The generated files are stored in `~/certs/{domain}`:

- **Root certificate key**: `{root_cert_name}.key`
- **Root certificate**: `{root_cert_name}.pem`
- **Domain private key**: `{domain}.key`
- **Signed certificate**: `{domain}.crt`

## Notes

- If running on non-macOS systems, you'll need to manually add the root certificate to your trusted certificate store.
- Ensure that OpenSSL is installed and available in your PATH.
62 changes: 62 additions & 0 deletions packages/twenty-server/scripts/ssl-generation/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash

# Check if OpenSSL is installed
if ! command -v openssl &> /dev/null
then
echo "OpenSSL is not installed. Please install it before running this script."
exit
fi

# Default values
DOMAIN=${1:-localhost.com}
ROOT_CERT_NAME=${2:-myRootCertificate}
VALIDITY_DAYS=${3:-398} # Default is 825 days

CERTS_DIR=~/certs/$DOMAIN

# Create a directory to store the certificates
mkdir -p $CERTS_DIR
cd $CERTS_DIR
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved

# Generate the private key for the Certificate Authority (CA)
openssl genrsa -aes256 -out ${ROOT_CERT_NAME}.key 2048

# Generate the root certificate for the CA
openssl req -x509 -new -nodes -key ${ROOT_CERT_NAME}.key -sha256 -days $VALIDITY_DAYS -out ${ROOT_CERT_NAME}.pem \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=MyUnit/CN=MyLocalCA"

# Add the root certificate to the macOS keychain (requires admin password)
if [[ "$OSTYPE" == "darwin"* ]]; then
sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" ${ROOT_CERT_NAME}.pem
fi

# Generate the private key for the provided domain
openssl genrsa -out $DOMAIN.key 2048

# Create a Certificate Signing Request (CSR) for the provided domain
openssl req -new -key $DOMAIN.key -out $DOMAIN.csr \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=MyUnit/CN=*.$DOMAIN"

# Create a configuration file for certificate extensions
cat > $DOMAIN.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = $DOMAIN
DNS.2 = *.$DOMAIN
EOF

# Sign the certificate with the CA
openssl x509 -req -in $DOMAIN.csr -CA ${ROOT_CERT_NAME}.pem -CAkey ${ROOT_CERT_NAME}.key -CAcreateserial \
-out $DOMAIN.crt -days $VALIDITY_DAYS -sha256 -extfile $DOMAIN.ext

echo "Certificates generated in the directory $CERTS_DIR:"
echo "- Root certificate: ${ROOT_CERT_NAME}.pem"
echo "- Domain private key: $DOMAIN.key"
echo "- Signed certificate: $DOMAIN.crt"

# Tips for usage
echo "To use these certificates with a local server, configure your server to use $DOMAIN.crt and $DOMAIN.key."
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ export class SSOAuthController {
);
} catch (err) {
// TODO: improve error management
res.status(403).send(err.message);
res.redirect(`${this.environmentService.get('FRONT_BASE_URL')}/verify`);
res
.status(403)
.redirect(`${this.environmentService.get('FRONT_BASE_URL')}/verify`);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,12 @@ export class EnvironmentVariables {
PG_SSL_ALLOW_SELF_SIGNED = false;

// Frontend URL
@IsUrl({ require_tld: false })
@IsUrl({ require_tld: false, require_protocol: true })
FRONT_BASE_URL: string;

// Server URL
@IsUrl({ require_tld: false })
@IsUrl({ require_tld: false, require_protocol: true })
@IsOptional()
SERVER_URL: string;
SERVER_URL = 'http://localhost:3000';

@IsString()
APP_SECRET: string;
Expand Down Expand Up @@ -166,10 +165,6 @@ export class EnvironmentVariables {
INVITATION_TOKEN_EXPIRES_IN = '30d';

// Auth
@IsUrl({ require_tld: false })
@IsOptional()
FRONT_AUTH_CALLBACK_URL: string;
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved

@CastToBoolean()
@IsOptional()
@IsBoolean()
Expand Down Expand Up @@ -198,11 +193,11 @@ export class EnvironmentVariables {
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
AUTH_MICROSOFT_CLIENT_SECRET: string;

@IsUrl({ require_tld: false })
@IsUrl({ require_tld: false, require_protocol: true })
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
AUTH_MICROSOFT_CALLBACK_URL: string;

@IsUrl({ require_tld: false })
@IsUrl({ require_tld: false, require_protocol: true })
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
AUTH_MICROSOFT_APIS_CALLBACK_URL: string;

Expand All @@ -219,7 +214,7 @@ export class EnvironmentVariables {
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
AUTH_GOOGLE_CLIENT_SECRET: string;

@IsUrl({ require_tld: false })
@IsUrl({ require_tld: false, require_protocol: true })
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
AUTH_GOOGLE_CALLBACK_URL: string;

Expand Down Expand Up @@ -475,6 +470,15 @@ export class EnvironmentVariables {
// milliseconds
@CastToPositiveNumber()
SERVERLESS_FUNCTION_EXEC_THROTTLE_TTL = 1000;

// SSL
@IsString()
@ValidateIf((env) => env.SERVER_URL.startsWith('https'))
SSL_KEY_PATH: string;
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved

@IsString()
@ValidateIf((env) => env.SERVER_URL.startsWith('https'))
SSL_CERT_PATH: string;
}

export const validate = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ export class SSOService {
buildIssuerURL(
identityProvider: Pick<WorkspaceSSOIdentityProvider, 'id' | 'type'>,
) {
return `${this.environmentService.get('SERVER_URL')}/auth/${identityProvider.type.toLowerCase()}/login/${identityProvider.id}`;
const authorizationUrl = new URL(this.environmentService.get('SERVER_URL'));

authorizationUrl.pathname = `/auth/${identityProvider.type.toLowerCase()}/login/${identityProvider.id}`;

return authorizationUrl.toString();
}

private isOIDCIdentityProvider(
Expand Down
12 changes: 11 additions & 1 deletion packages/twenty-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';

import fs from 'fs';

import session from 'express-session';
import bytes from 'bytes';
import { useContainer } from 'class-validator';
Expand All @@ -24,6 +26,14 @@ const bootstrap = async () => {
bufferLogs: process.env.LOGGER_IS_BUFFER_ENABLED === 'true',
rawBody: true,
snapshot: process.env.DEBUG_MODE === 'true',
...(process.env.SSL_KEY_PATH && process.env.SSL_CERT_PATH
? {
httpsOptions: {
key: fs.readFileSync(process.env.SSL_KEY_PATH),
cert: fs.readFileSync(process.env.SSL_CERT_PATH),
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved
},
}
: {}),
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved
});
const logger = app.get(LoggerService);
const environmentService = app.get(EnvironmentService);
Expand Down Expand Up @@ -68,7 +78,7 @@ const bootstrap = async () => {
app.use(session(getSessionStorageOptions(environmentService)));
}

await app.listen(process.env.PORT ?? 3000);
await app.listen(environmentService.get('PORT'));
};

bootstrap();
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ yarn command:prod cron:calendar:calendar-event-list-fetch
['AUTH_MICROSOFT_CLIENT_SECRET', '', 'Microsoft client secret'],
['AUTH_MICROSOFT_CALLBACK_URL', 'http://[YourDomain]/auth/microsoft/redirect', 'Microsoft auth callback'],
['AUTH_MICROSOFT_APIS_CALLBACK_URL', 'http://[YourDomain]/auth/microsoft-apis/get-access-token', 'Microsoft APIs auth callback'],
['FRONT_AUTH_CALLBACK_URL', 'http://localhost:3001/verify ', 'Callback used for Login page'],
['IS_SIGN_UP_DISABLED', 'false', 'Disable sign-up'],
['PASSWORD_RESET_TOKEN_EXPIRES_IN', '5m', 'Password reset token expiration time'],
]}></ArticleTable>
Expand Down
Loading