feat: add configurable session cookie name (#5425)
Added support for overriding the default session cookie name using the `INFRA.SESSION_COOKIE_NAME` config or the `SESSION_COOKIE_NAME` environment variable. This helps compatibility with proxies or load balancers that cannot handle cookie names containing dots. --- Co-authored-by: mirarifhasan <arif.ishan05@gmail.com> Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
8f7146bd57
commit
453b5fc088
8 changed files with 68 additions and 3 deletions
|
|
@ -127,6 +127,11 @@ export async function getDefaultInfraConfigs(): Promise<DefaultInfraConfig[]> {
|
|||
value: encrypt(randomBytes(32).toString('hex')),
|
||||
isEncrypted: true,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.SESSION_COOKIE_NAME,
|
||||
value: null,
|
||||
isEncrypted: false,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.TOKEN_SALT_COMPLEXITY,
|
||||
value: '10',
|
||||
|
|
|
|||
|
|
@ -734,6 +734,11 @@ export class InfraConfigService implements OnModuleInit {
|
|||
return fail();
|
||||
break;
|
||||
|
||||
case InfraConfigEnum.SESSION_COOKIE_NAME:
|
||||
// Allow empty to fall back to default; otherwise enforce allowed characters
|
||||
if (value && !/^[A-Za-z0-9_-]+$/.test(value)) return fail();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,13 @@ async function bootstrap() {
|
|||
|
||||
app.use(
|
||||
session({
|
||||
// Allow overriding the default cookie name 'connect.sid' (which contains a dot).
|
||||
// Some proxies/load balancers (like older Kong versions) cannot hash cookie names with dots,
|
||||
// so we allow setting an alternative name via the INFRA.SESSION_COOKIE_NAME configuration.
|
||||
name:
|
||||
configService.get<string>('INFRA.SESSION_COOKIE_NAME') || 'connect.sid',
|
||||
secret:
|
||||
configService.get('INFRA.SESSION_SECRET') ||
|
||||
configService.get<string>('INFRA.SESSION_SECRET') ||
|
||||
crypto.randomBytes(16).toString('hex'),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export enum InfraConfigEnum {
|
|||
|
||||
JWT_SECRET = 'JWT_SECRET',
|
||||
SESSION_SECRET = 'SESSION_SECRET',
|
||||
SESSION_COOKIE_NAME = 'SESSION_COOKIE_NAME',
|
||||
TOKEN_SALT_COMPLEXITY = 'TOKEN_SALT_COMPLEXITY',
|
||||
MAGIC_LINK_TOKEN_VALIDITY = 'MAGIC_LINK_TOKEN_VALIDITY',
|
||||
REFRESH_TOKEN_VALIDITY = 'REFRESH_TOKEN_VALIDITY',
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@
|
|||
"refresh_token_validity": "Refresh Token Validity (in milliseconds)",
|
||||
"access_token_validity": "Access Token Validity (in milliseconds)",
|
||||
"session_secret": "Session Secret",
|
||||
"session_cookie_name": "Session Cookie Name (optional)",
|
||||
"session_cookie_name_help": "Only letters, numbers, underscore, and hyphen. Leave empty to use default 'connect.sid'.",
|
||||
"session_cookie_name_invalid": "Invalid cookie name. Only letters, numbers, underscore, and hyphen allowed.",
|
||||
"update_failure": "Failed to update token configurations!!"
|
||||
},
|
||||
"update_failure": "Failed to update authentication provider configurations!!"
|
||||
|
|
|
|||
|
|
@ -133,6 +133,20 @@
|
|||
</template>
|
||||
</HoppSmartInput>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label>{{
|
||||
t('configs.auth_providers.token.session_cookie_name')
|
||||
}}</label>
|
||||
<HoppSmartInput
|
||||
v-model="authTokenConfig.fields.session_cookie_name"
|
||||
placeholder="e.g., connect_sid"
|
||||
:autofocus="false"
|
||||
class="!my-2 !bg-primaryLight flex-1 border border-divider rounded"
|
||||
/>
|
||||
<p class="my-1 text-secondaryLight">
|
||||
{{ t('configs.auth_providers.token.session_cookie_name_help') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,12 +28,19 @@ import {
|
|||
MICROSOFT_CONFIGS,
|
||||
MOCK_SERVER_CONFIGS,
|
||||
ServerConfigs,
|
||||
TOKEN_VALIDATION_CONFIGS,
|
||||
UpdatedConfigs,
|
||||
} from '~/helpers/configs';
|
||||
import { getCompiledErrorMessage } from '~/helpers/errors';
|
||||
import { useToast } from './toast';
|
||||
import { useClientHandler } from './useClientHandler';
|
||||
|
||||
const COOKIE_NAME_REGEX = /^[A-Za-z0-9_-]+$/;
|
||||
|
||||
const OPTIONAL_TOKEN_FIELD_KEYS = new Set(
|
||||
TOKEN_VALIDATION_CONFIGS.filter((cfg) => cfg.optional).map((cfg) => cfg.key)
|
||||
);
|
||||
|
||||
/** Composable that handles all operations related to server configurations
|
||||
* @param updatedConfigs A Config Object containing the updated configs
|
||||
*/
|
||||
|
|
@ -154,6 +161,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
InfraConfigEnum.AccessTokenValidity
|
||||
),
|
||||
session_secret: getFieldValue(InfraConfigEnum.SessionSecret),
|
||||
session_cookie_name: getFieldValue(InfraConfigEnum.SessionCookieName),
|
||||
},
|
||||
},
|
||||
dataSharingConfigs: {
|
||||
|
|
@ -286,8 +294,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
|
||||
// This section has no enabled property, so we check fields directly
|
||||
// for a valid number (>0) or non-empty string
|
||||
if (section.name === 'token')
|
||||
return Object.values(section.fields).some(isFieldNotValid);
|
||||
if (section.name === 'token') {
|
||||
return Object.entries(section.fields).some(
|
||||
([key, value]) =>
|
||||
!OPTIONAL_TOKEN_FIELD_KEYS.has(key) && isFieldNotValid(value)
|
||||
);
|
||||
}
|
||||
|
||||
// For rate limit section, we want to check if the values are not valid numbers
|
||||
// and not empty strings
|
||||
|
|
@ -573,6 +585,14 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
const sessionSecret = String(
|
||||
updatedConfigs?.tokenConfigs.fields.session_secret
|
||||
);
|
||||
const sessionCookieName = String(
|
||||
updatedConfigs?.tokenConfigs.fields.session_cookie_name || ''
|
||||
);
|
||||
// Validate cookie name: allow empty (falls back to default), else enforce pattern
|
||||
if (sessionCookieName && !COOKIE_NAME_REGEX.test(sessionCookieName)) {
|
||||
toast.error(t('configs.auth_providers.token.session_cookie_name_invalid'));
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
isFieldEmpty(jwtSecret) ||
|
||||
isFieldEmpty(tokenSaltComplexity) ||
|
||||
|
|
@ -610,6 +630,10 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
name: InfraConfigEnum.SessionSecret,
|
||||
value: sessionSecret,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.SessionCookieName,
|
||||
value: sessionCookieName,
|
||||
},
|
||||
];
|
||||
|
||||
return executeMutation(
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export type ServerConfigs = {
|
|||
refresh_token_validity: string;
|
||||
access_token_validity: string;
|
||||
session_secret: string;
|
||||
session_cookie_name: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -116,6 +117,8 @@ export type ConfigSection = {
|
|||
export type Config = {
|
||||
name: InfraConfigEnum;
|
||||
key: string;
|
||||
// Marks fields that are optional and should be excluded from mandatory validation
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
export const GOOGLE_CONFIGS: Config[] = [
|
||||
|
|
@ -258,6 +261,11 @@ export const TOKEN_VALIDATION_CONFIGS: Config[] = [
|
|||
name: InfraConfigEnum.SessionSecret,
|
||||
key: 'session_secret',
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.SessionCookieName,
|
||||
key: 'session_cookie_name',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.TokenSaltComplexity,
|
||||
key: 'token_salt_complexity',
|
||||
|
|
|
|||
Loading…
Reference in a new issue