support user configuration of auth cookie max age
Signed-off-by: Jessica He <jhe@redhat.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
import { CookieOptions, Request, Response } from 'express';
|
||||
import { CookieConfigurer } from '../types';
|
||||
import { HumanDuration, durationToMilliseconds } from '@backstage/types';
|
||||
|
||||
const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
const TEN_MINUTES_MS = 600 * 1000;
|
||||
@@ -55,6 +56,7 @@ export class OAuthCookieManager {
|
||||
private readonly nonceCookie: string;
|
||||
private readonly refreshTokenCookie: string;
|
||||
private readonly grantedScopeCookie: string;
|
||||
private readonly maxAge: number;
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
@@ -63,6 +65,7 @@ export class OAuthCookieManager {
|
||||
baseUrl: string;
|
||||
callbackUrl: string;
|
||||
cookieConfigurer?: CookieConfigurer;
|
||||
sessionDuration?: HumanDuration;
|
||||
},
|
||||
) {
|
||||
this.cookieConfigurer = options.cookieConfigurer ?? defaultCookieConfigurer;
|
||||
@@ -70,6 +73,9 @@ export class OAuthCookieManager {
|
||||
this.nonceCookie = `${options.providerId}-nonce`;
|
||||
this.refreshTokenCookie = `${options.providerId}-refresh-token`;
|
||||
this.grantedScopeCookie = `${options.providerId}-granted-scope`;
|
||||
this.maxAge = options.sessionDuration
|
||||
? durationToMilliseconds(options.sessionDuration)
|
||||
: THOUSAND_DAYS_MS;
|
||||
}
|
||||
|
||||
private getConfig(origin?: string, pathSuffix: string = '') {
|
||||
@@ -103,7 +109,7 @@ export class OAuthCookieManager {
|
||||
res,
|
||||
this.refreshTokenCookie,
|
||||
refreshToken,
|
||||
THOUSAND_DAYS_MS,
|
||||
this.maxAge,
|
||||
origin,
|
||||
);
|
||||
}
|
||||
@@ -117,13 +123,7 @@ export class OAuthCookieManager {
|
||||
}
|
||||
|
||||
setGrantedScopes(res: Response, scope: string, origin?: string): void {
|
||||
this.setCookie(
|
||||
res,
|
||||
this.grantedScopeCookie,
|
||||
scope,
|
||||
THOUSAND_DAYS_MS,
|
||||
origin,
|
||||
);
|
||||
this.setCookie(res, this.grantedScopeCookie, scope, this.maxAge, origin);
|
||||
}
|
||||
|
||||
getNonce(req: Request): string | undefined {
|
||||
|
||||
@@ -813,6 +813,75 @@ describe('createOAuthRouteHandlers', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set sessionDuration to configured value', async () => {
|
||||
const baseConfigWithSessionDuration = {
|
||||
...baseConfig,
|
||||
config: new ConfigReader({
|
||||
sessionDuration: { days: 7 },
|
||||
}),
|
||||
};
|
||||
|
||||
const agent = request.agent(
|
||||
wrapInApp(createOAuthRouteHandlers(baseConfigWithSessionDuration)),
|
||||
);
|
||||
|
||||
agent.jar.setCookie(
|
||||
'my-provider-refresh-token=refresh-token',
|
||||
'127.0.0.1',
|
||||
'/my-provider',
|
||||
);
|
||||
|
||||
mockAuthenticator.refresh.mockImplementation(async ({ scope }) => ({
|
||||
fullProfile: { id: 'id' } as PassportProfile,
|
||||
session: { ...mockSession, scope, refreshToken: 'new-refresh-token' },
|
||||
}));
|
||||
|
||||
const res = await agent
|
||||
.post('/my-provider/refresh')
|
||||
.set('X-Requested-With', 'XMLHttpRequest');
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const expectedExpirationDate = Date.now() + 7 * 24 * 60 * 60 * 1000;
|
||||
const cookie = getRefreshTokenCookie(agent);
|
||||
expect(cookie.expiration_date).toBeGreaterThanOrEqual(
|
||||
expectedExpirationDate - 1000,
|
||||
);
|
||||
expect(cookie.expiration_date).toBeLessThanOrEqual(
|
||||
expectedExpirationDate + 1000,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set sessionDuration to default of 1000 days when not configured', async () => {
|
||||
const agent = request.agent(
|
||||
wrapInApp(createOAuthRouteHandlers(baseConfig)),
|
||||
);
|
||||
|
||||
agent.jar.setCookie(
|
||||
'my-provider-refresh-token=refresh-token',
|
||||
'127.0.0.1',
|
||||
'/my-provider',
|
||||
);
|
||||
|
||||
mockAuthenticator.refresh.mockImplementation(async ({ scope }) => ({
|
||||
fullProfile: { id: 'id' } as PassportProfile,
|
||||
session: { ...mockSession, scope, refreshToken: 'new-refresh-token' },
|
||||
}));
|
||||
|
||||
const res = await agent
|
||||
.post('/my-provider/refresh')
|
||||
.set('X-Requested-With', 'XMLHttpRequest');
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const expectedExpirationDate = Date.now() + 1000 * 24 * 60 * 60 * 1000;
|
||||
const cookie = getRefreshTokenCookie(agent);
|
||||
expect(cookie.expiration_date).toBeGreaterThanOrEqual(
|
||||
expectedExpirationDate - 1000,
|
||||
);
|
||||
expect(cookie.expiration_date).toBeLessThanOrEqual(
|
||||
expectedExpirationDate + 1000,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import { OAuthAuthenticator, OAuthAuthenticatorResult } from './types';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Config, readDurationFromConfig } from '@backstage/config';
|
||||
import { CookieScopeManager } from './CookieScopeManager';
|
||||
|
||||
/** @public */
|
||||
@@ -99,6 +99,9 @@ export function createOAuthRouteHandlers<TProfile>(
|
||||
const callbackUrl =
|
||||
config.getOptionalString('callbackUrl') ??
|
||||
`${baseUrl}/${providerId}/handler/frame`;
|
||||
const sessionDuration = config.has('sessionDuration')
|
||||
? readDurationFromConfig(config, { key: 'sessionDuration' })
|
||||
: undefined;
|
||||
|
||||
const stateTransform = options.stateTransform ?? (state => ({ state }));
|
||||
const profileTransform =
|
||||
@@ -110,6 +113,7 @@ export function createOAuthRouteHandlers<TProfile>(
|
||||
defaultAppOrigin,
|
||||
providerId,
|
||||
cookieConfigurer,
|
||||
sessionDuration,
|
||||
});
|
||||
|
||||
const scopeManager = CookieScopeManager.create({
|
||||
|
||||
Reference in New Issue
Block a user