diff --git a/eslint.config.js b/eslint.config.js index fb1e9c1d..704cad87 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' module.exports = require('neostandard')({ - semi: true, + semi: false, ignores: ['dist/**/*', 'node_modules/**/*', 'coverage/**/*'], ts: true, -}); +}) diff --git a/src/AuthenticationRoute.ts b/src/AuthenticationRoute.ts index 3bf8dc67..39376e91 100644 --- a/src/AuthenticationRoute.ts +++ b/src/AuthenticationRoute.ts @@ -1,16 +1,16 @@ -import * as http from 'node:http'; -import AuthenticationError from './errors'; -import Authenticator from './Authenticator'; -import { AnyStrategy, Strategy } from './strategies'; -import { FastifyReply, FastifyRequest } from 'fastify'; -import { types } from 'node:util'; - -type FlashObject = { type?: string; message?: string }; +import * as http from 'node:http' +import AuthenticationError from './errors' +import Authenticator from './Authenticator' +import { AnyStrategy, Strategy } from './strategies' +import { FastifyReply, FastifyRequest } from 'fastify' +import { types } from 'node:util' + +type FlashObject = { type?: string; message?: string } type FailureObject = { challenge?: string | FlashObject status?: number type?: string -}; +} declare module '@fastify/secure-session' { interface SessionData { @@ -20,10 +20,10 @@ declare module '@fastify/secure-session' { } const addMessage = (request: FastifyRequest, message: string) => { - const existing = request.session.get('messages'); - const messages = existing ? [...existing, message] : [message]; - request.session.set('messages', messages); -}; + const existing = request.session.get('messages') + const messages = existing ? [...existing, message] : [message] + request.session.set('messages', messages) +} export interface AuthenticateOptions { scope?: string | string[] @@ -50,7 +50,7 @@ export type SingleStrategyCallback = ( user?: unknown, info?: unknown, status?: number -) => Promise; +) => Promise export type MultiStrategyCallback = ( request: FastifyRequest, reply: FastifyReply, @@ -58,17 +58,17 @@ export type MultiStrategyCallback = ( user?: unknown, info?: unknown, statuses?: (number | undefined)[] -) => Promise; +) => Promise export type AuthenticateCallback = - StrategyOrStrategies extends any[] ? MultiStrategyCallback : SingleStrategyCallback; + StrategyOrStrategies extends any[] ? MultiStrategyCallback : SingleStrategyCallback -const Unhandled = Symbol.for('passport-unhandled'); +const Unhandled = Symbol.for('passport-unhandled') export class AuthenticationRoute { - readonly options: AuthenticateOptions; - readonly strategies: (string | Strategy)[]; - readonly isMultiStrategy: boolean; + readonly options: AuthenticateOptions + readonly strategies: (string | Strategy)[] + readonly isMultiStrategy: boolean /** * Create a new route handler that runs authentication strategies. @@ -84,25 +84,25 @@ export class AuthenticationRoute ) { - this.options = options || {}; + this.options = options || {} // Cast `name` to an array, allowing authentication to pass through a chain of strategies. The first strategy to succeed, redirect, or error will halt the chain. Authentication failures will proceed through each strategy in series, ultimately failing if all strategies fail. // This is typically used on API endpoints to allow clients to authenticate using their preferred choice of Basic, Digest, token-based schemes, etc. It is not feasible to construct a chain of multiple strategies that involve redirection (for example both Facebook and Twitter), since the first one to redirect will halt the chain. if (Array.isArray(strategyOrStrategies)) { - this.strategies = strategyOrStrategies; - this.isMultiStrategy = false; + this.strategies = strategyOrStrategies + this.isMultiStrategy = false } else { - this.strategies = [strategyOrStrategies]; - this.isMultiStrategy = false; + this.strategies = [strategyOrStrategies] + this.isMultiStrategy = false } } handler = async (request: FastifyRequest, reply: FastifyReply) => { if (!request.passport) { - throw new Error('passport.initialize() plugin not in use'); + throw new Error('passport.initialize() plugin not in use') } // accumulator for failures from each strategy in the chain - const failures: FailureObject[] = []; + const failures: FailureObject[] = [] for (const nameOrInstance of this.strategies) { try { @@ -112,18 +112,18 @@ export class AuthenticationRoute((resolve, reject) => { @@ -142,17 +142,17 @@ export class AuthenticationRoute { - request.log.debug({ strategy: name }, 'passport strategy success'); + request.log.debug({ strategy: name }, 'passport strategy success') if (this.callback) { - return resolve(this.callback(request, reply, null, user, info)); + return resolve(this.callback(request, reply, null, user, info)) } - info = info || {}; - this.applyFlashOrMessage('success', request, info); + info = info || {} + this.applyFlashOrMessage('success', request, info) if (this.options.assignProperty) { - request[this.options.assignProperty] = user; - return resolve(); + request[this.options.assignProperty] = user + return resolve() } request @@ -161,33 +161,33 @@ export class AuthenticationRoute { const complete = () => { if (this.options.successReturnToOrRedirect) { - let url = this.options.successReturnToOrRedirect; - const returnTo = request.session?.get('returnTo'); + let url = this.options.successReturnToOrRedirect + const returnTo = request.session?.get('returnTo') if (typeof returnTo === 'string') { - url = returnTo; - request.session.set('returnTo', undefined); + url = returnTo + request.session.set('returnTo', undefined) } - reply.redirect(url); + reply.redirect(url) } else if (this.options.successRedirect) { - reply.redirect(this.options.successRedirect); + reply.redirect(this.options.successRedirect) } - return resolve(); - }; + return resolve() + } if (this.options.authInfo !== false) { this.authenticator .transformAuthInfo(info, request) .catch(reject) .then((transformedInfo) => { - request.authInfo = transformedInfo; - complete(); - }); + request.authInfo = transformedInfo + complete() + }) } else { - complete(); + complete() } - }); - }; + }) + } /** * Fail authentication, with optional `challenge` and `status`, defaulting to 401. @@ -195,20 +195,20 @@ export class AuthenticationRoute { - request.log.trace({ strategy: name, url }, 'passport strategy redirecting'); + request.log.trace({ strategy: name, url }, 'passport strategy redirecting') - reply.status(status || 302); - reply.redirect(url); - resolve(); - }; + reply.status(status || 302) + reply.redirect(url) + resolve() + } /** * Pass without making a success or fail decision. @@ -229,49 +229,49 @@ export class AuthenticationRoute { - request.log.trace({ strategy: name }, 'passport strategy passed'); + request.log.trace({ strategy: name }, 'passport strategy passed') - resolve(); - }; + resolve() + } const error = (err: Error) => { - request.log.trace({ strategy: name, err }, 'passport strategy errored'); + request.log.trace({ strategy: name, err }, 'passport strategy errored') if (this.callback) { - return resolve(this.callback(request, reply, err)); + return resolve(this.callback(request, reply, err)) } - reject(err); - }; + reject(err) + } /** * Internal error while performing authentication. * * Strategies should call this function when an internal error occurs during the process of performing authentication; for example, if the user directory is not available. */ - strategy.error = error; + strategy.error = error - request.log.trace({ strategy: name }, 'attempting passport strategy authentication'); + request.log.trace({ strategy: name }, 'attempting passport strategy authentication') try { - const result = strategy.authenticate(request, this.options); + const result = strategy.authenticate(request, this.options) if (types.isPromise(result)) { - result.catch(error); + result.catch(error) } } catch (err) { - error(err as Error); + error(err as Error) } - }); + }) } async onAllFailed (failures: FailureObject[], request: FastifyRequest, reply: FastifyReply) { - request.log.trace('all passport strategies failed'); + request.log.trace('all passport strategies failed') if (this.callback) { if (this.isMultiStrategy) { - const challenges = failures.map((f) => f.challenge); - const statuses = failures.map((f) => f.status); - return await (this.callback as MultiStrategyCallback)(request, reply, null, false, challenges, statuses); + const challenges = failures.map((f) => f.challenge) + const statuses = failures.map((f) => f.status) + return await (this.callback as MultiStrategyCallback)(request, reply, null, false, challenges, statuses) } else { return await (this.callback as SingleStrategyCallback)( request, @@ -280,63 +280,63 @@ export class AuthenticationRoute = ( user: User, req: FastifyRequest -) => Promise; +) => Promise export type DeserializeFunction = ( serialized: SerializedUser, req: FastifyRequest -) => Promise; +) => Promise -export type InfoTransformerFunction = (info: any) => Promise; +export type InfoTransformerFunction = (info: any) => Promise export interface AuthenticatorOptions { key?: string @@ -26,24 +26,24 @@ export interface AuthenticatorOptions { export class Authenticator { // a Fastify-instance wide unique string identifying this instance of fastify-passport (default: "passport") - public key: string; + public key: string // the key on the request at which to store the deserialized user value (default: "user") - public userProperty: string; - public sessionManager: SecureSessionManager; + public userProperty: string + public sessionManager: SecureSessionManager - private strategies: { [k: string]: AnyStrategy } = {}; - private serializers: SerializeFunction[] = []; - private deserializers: DeserializeFunction[] = []; - private infoTransformers: InfoTransformerFunction[] = []; - private clearSessionOnLogin: boolean; - private clearSessionIgnoreFields: string[]; + private strategies: { [k: string]: AnyStrategy } = {} + private serializers: SerializeFunction[] = [] + private deserializers: DeserializeFunction[] = [] + private infoTransformers: InfoTransformerFunction[] = [] + private clearSessionOnLogin: boolean + private clearSessionIgnoreFields: string[] constructor (options: AuthenticatorOptions = {}) { - this.key = options.key || 'passport'; - this.userProperty = options.userProperty || 'user'; - this.use(new SessionStrategy(this.deserializeUser.bind(this))); - this.clearSessionOnLogin = options.clearSessionOnLogin ?? true; - this.clearSessionIgnoreFields = ['passport', 'session', ...(options.clearSessionIgnoreFields || [])]; + this.key = options.key || 'passport' + this.userProperty = options.userProperty || 'user' + this.use(new SessionStrategy(this.deserializeUser.bind(this))) + this.clearSessionOnLogin = options.clearSessionOnLogin ?? true + this.clearSessionIgnoreFields = ['passport', 'session', ...(options.clearSessionIgnoreFields || [])] this.sessionManager = new SecureSessionManager( { key: this.key, @@ -51,31 +51,31 @@ export class Authenticator { clearSessionIgnoreFields: this.clearSessionIgnoreFields }, this.serializeUser.bind(this) - ); + ) } - use (strategy: AnyStrategy): this; - use (name: string, strategy: AnyStrategy): this; + use (strategy: AnyStrategy): this + use (name: string, strategy: AnyStrategy): this use (name: AnyStrategy | string, strategy?: AnyStrategy): this { if (!strategy) { - strategy = name as AnyStrategy; - name = strategy.name as string; + strategy = name as AnyStrategy + name = strategy.name as string } if (!name) { - throw new Error('Authentication strategies must have a name'); + throw new Error('Authentication strategies must have a name') } - this.strategies[name as string] = strategy; - return this; + this.strategies[name as string] = strategy + return this } public unuse (name: string): this { - delete this.strategies[name]; - return this; + delete this.strategies[name] + return this } public initialize (): FastifyPluginAsync { - return CreateInitializePlugin(this); + return CreateInitializePlugin(this) } /** @@ -146,30 +146,30 @@ export class Authenticator { public authenticate( strategy: StrategyOrStrategies, callback?: AuthenticateCallback - ): RouteHandlerMethod; + ): RouteHandlerMethod public authenticate( strategy: StrategyOrStrategies, options?: AuthenticateOptions - ): RouteHandlerMethod; + ): RouteHandlerMethod public authenticate( strategy: StrategyOrStrategies, options?: AuthenticateOptions, callback?: AuthenticateCallback - ): RouteHandlerMethod; + ): RouteHandlerMethod public authenticate( strategyOrStrategies: StrategyOrStrategies, optionsOrCallback?: AuthenticateOptions | AuthenticateCallback, callback?: AuthenticateCallback ): RouteHandlerMethod { - let options: AuthenticateOptions | undefined; + let options: AuthenticateOptions | undefined if (typeof optionsOrCallback === 'function') { - options = {}; - callback = optionsOrCallback; + options = {} + callback = optionsOrCallback } else { - options = optionsOrCallback; + options = optionsOrCallback } - return new AuthenticationRoute(this, strategyOrStrategies, options, callback).handler; + return new AuthenticationRoute(this, strategyOrStrategies, options, callback).handler } /** @@ -192,32 +192,32 @@ export class Authenticator { public authorize( strategy: StrategyOrStrategies, callback?: AuthenticateCallback - ): RouteHandlerMethod; + ): RouteHandlerMethod public authorize( strategy: StrategyOrStrategies, options?: AuthenticateOptions - ): RouteHandlerMethod; + ): RouteHandlerMethod public authorize( strategy: StrategyOrStrategies, options?: AuthenticateOptions, callback?: AuthenticateCallback - ): RouteHandlerMethod; + ): RouteHandlerMethod public authorize( strategyOrStrategies: StrategyOrStrategies, optionsOrCallback?: AuthenticateOptions | AuthenticateCallback, callback?: AuthenticateCallback ): RouteHandlerMethod { - let options: AuthenticateOptions | undefined; + let options: AuthenticateOptions | undefined if (typeof optionsOrCallback === 'function') { - options = {}; - callback = optionsOrCallback; + options = {} + callback = optionsOrCallback } else { - options = optionsOrCallback; + options = optionsOrCallback } - options || (options = {}); - options.assignProperty = 'account'; + options || (options = {}) + options.assignProperty = 'account' - return new AuthenticationRoute(this, strategyOrStrategies, options, callback).handler; + return new AuthenticationRoute(this, strategyOrStrategies, options, callback).handler } /** @@ -246,8 +246,8 @@ export class Authenticator { */ public secureSession (options?: AuthenticateOptions): FastifyPluginAsync { return fastifyPlugin(async (fastify) => { - fastify.addHook('preValidation', new AuthenticationRoute(this, 'session', options).handler); - }); + fastify.addHook('preValidation', new AuthenticationRoute(this, 'session', options).handler) + }) } /** @@ -260,17 +260,17 @@ export class Authenticator { * @api public */ registerUserSerializer(fn: SerializeFunction) { - this.serializers.push(fn); + this.serializers.push(fn) } /** Runs the chain of serializers to find the first one that serializes a user, and returns it. */ async serializeUser(user: User, request: FastifyRequest): Promise { - const result = await this.runStack(this.serializers, user, request); + const result = await this.runStack(this.serializers, user, request) if (result) { - return result; + return result } else { - throw new Error(`Failed to serialize user into session. Tried ${this.serializers.length} serializers.`); + throw new Error(`Failed to serialize user into session. Tried ${this.serializers.length} serializers.`) } } @@ -286,18 +286,18 @@ export class Authenticator { * @api public */ registerUserDeserializer(fn: DeserializeFunction) { - this.deserializers.push(fn); + this.deserializers.push(fn) } async deserializeUser(stored: StoredUser, request: FastifyRequest): Promise { - const result = await this.runStack(this.deserializers, stored, request); + const result = await this.runStack(this.deserializers, stored, request) if (result) { - return result; + return result } else if (result === null || result === false) { - return false; + return false } else { - throw new Error(`Failed to deserialize user out of session. Tried ${this.deserializers.length} serializers.`); + throw new Error(`Failed to deserialize user out of session. Tried ${this.deserializers.length} serializers.`) } } @@ -324,13 +324,13 @@ export class Authenticator { * @api public */ registerAuthInfoTransformer (fn: InfoTransformerFunction) { - this.infoTransformers.push(fn); + this.infoTransformers.push(fn) } async transformAuthInfo (info: any, request: FastifyRequest) { - const result = await this.runStack(this.infoTransformers, info, request); + const result = await this.runStack(this.infoTransformers, info, request) // if no transformers are registered (or they all pass), the default behavior is to use the un-transformed info as-is - return result || info; + return result || info } /** @@ -341,22 +341,22 @@ export class Authenticator { * @api private */ strategy (name: string): AnyStrategy | undefined { - return this.strategies[name]; + return this.strategies[name] } private async runStack(stack: ((...args: [A, B]) => Promise)[], ...args: [A, B]) { for (const attempt of stack) { try { - return await attempt(...args); + return await attempt(...args) } catch (e) { if (e === 'pass') { - continue; + continue } else { - throw e; + throw e } } } } } -export default Authenticator; +export default Authenticator diff --git a/src/CreateInitializePlugin.ts b/src/CreateInitializePlugin.ts index 029ee880..070e6b7e 100644 --- a/src/CreateInitializePlugin.ts +++ b/src/CreateInitializePlugin.ts @@ -1,22 +1,22 @@ -import fp from 'fastify-plugin'; -import { logIn, logOut, isAuthenticated, isUnauthenticated } from './decorators'; -import Authenticator from './Authenticator'; -import flash = require('@fastify/flash'); +import fp from 'fastify-plugin' +import { logIn, logOut, isAuthenticated, isUnauthenticated } from './decorators' +import Authenticator from './Authenticator' +import flash = require('@fastify/flash') export function CreateInitializePlugin (passport: Authenticator) { return fp(async (fastify) => { - fastify.register(flash); + fastify.register(flash) fastify.decorateRequest('passport', { getter () { - return passport; + return passport } - }); - fastify.decorateRequest('logIn', logIn); - fastify.decorateRequest('login', logIn); - fastify.decorateRequest('logOut', logOut); - fastify.decorateRequest('logout', logOut); - fastify.decorateRequest('isAuthenticated', isAuthenticated); - fastify.decorateRequest('isUnauthenticated', isUnauthenticated); - fastify.decorateRequest(passport.userProperty, null); - }); + }) + fastify.decorateRequest('logIn', logIn) + fastify.decorateRequest('login', logIn) + fastify.decorateRequest('logOut', logOut) + fastify.decorateRequest('logout', logOut) + fastify.decorateRequest('isAuthenticated', isAuthenticated) + fastify.decorateRequest('isUnauthenticated', isUnauthenticated) + fastify.decorateRequest(passport.userProperty, null) + }) } diff --git a/src/decorators/index.ts b/src/decorators/index.ts index 5d7da793..b9505416 100644 --- a/src/decorators/index.ts +++ b/src/decorators/index.ts @@ -1,6 +1,6 @@ -import { logIn } from './login'; -import { logOut } from './logout'; -import { isAuthenticated } from './is-authenticated'; -import { isUnauthenticated } from './is-unauthenticated'; +import { logIn } from './login' +import { logOut } from './logout' +import { isAuthenticated } from './is-authenticated' +import { isUnauthenticated } from './is-unauthenticated' -export { logIn, logOut, isAuthenticated, isUnauthenticated }; +export { logIn, logOut, isAuthenticated, isUnauthenticated } diff --git a/src/decorators/is-authenticated.ts b/src/decorators/is-authenticated.ts index 3ece11f0..88bf7eca 100644 --- a/src/decorators/is-authenticated.ts +++ b/src/decorators/is-authenticated.ts @@ -1,6 +1,6 @@ -import { FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify' export function isAuthenticated (this: FastifyRequest): boolean { - const property = this.passport.userProperty; - return !!this[property]; + const property = this.passport.userProperty + return !!this[property] } diff --git a/src/decorators/is-unauthenticated.ts b/src/decorators/is-unauthenticated.ts index 41f68272..e1b498c3 100644 --- a/src/decorators/is-unauthenticated.ts +++ b/src/decorators/is-unauthenticated.ts @@ -1,5 +1,5 @@ -import { FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify' export function isUnauthenticated (this: FastifyRequest): boolean { - return !this.isAuthenticated(); + return !this.isAuthenticated() } diff --git a/src/decorators/login.ts b/src/decorators/login.ts index 3d62d3aa..e08404a2 100644 --- a/src/decorators/login.ts +++ b/src/decorators/login.ts @@ -1,6 +1,6 @@ -import { FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify' -export type DoneCallback = (err?: Error) => void; +export type DoneCallback = (err?: Error) => void /** * Initiate a login session for `user`. * @@ -21,31 +21,31 @@ export type DoneCallback = (err?: Error) => void; * @param {Function} done * @api public */ -export async function logIn (this: FastifyRequest, user: T): Promise; +export async function logIn (this: FastifyRequest, user: T): Promise export async function logIn ( this: FastifyRequest, user: T, options: { session?: boolean; keepSessionInfo?: boolean } -): Promise; +): Promise export async function logIn ( this: FastifyRequest, user: T, options: { session?: boolean; keepSessionInfo?: boolean } = {} ) { if (!this.passport) { - throw new Error('passport.initialize() plugin not in use'); + throw new Error('passport.initialize() plugin not in use') } - const property = this.passport.userProperty; - const session = options.session === undefined ? true : options.session; + const property = this.passport.userProperty + const session = options.session === undefined ? true : options.session - this[property] = user; + this[property] = user if (session) { try { - await this.passport.sessionManager.logIn(this, user, options); + await this.passport.sessionManager.logIn(this, user, options) } catch (e) { - this[property] = null; - throw e; + this[property] = null + throw e } } } diff --git a/src/decorators/logout.ts b/src/decorators/logout.ts index 9e1e058b..ada5294b 100644 --- a/src/decorators/logout.ts +++ b/src/decorators/logout.ts @@ -1,4 +1,4 @@ -import { FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify' /** * Terminate an existing login session. @@ -6,7 +6,7 @@ import { FastifyRequest } from 'fastify'; * @api public */ export async function logOut (this: FastifyRequest): Promise { - const property = this.passport.userProperty; - this[property] = null; - await this.passport.sessionManager.logOut(this); + const property = this.passport.userProperty + this[property] = null + await this.passport.sessionManager.logOut(this) } diff --git a/src/errors.ts b/src/errors.ts index 07c7fcaf..c16092d3 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,14 +1,14 @@ class AuthenticationError extends Error { - status: number; + status: number constructor (message: string, status: number) { - super(); + super() - Error.captureStackTrace(this, this.constructor); - this.name = 'AuthenticationError'; - this.message = message; - this.status = status || 401; + Error.captureStackTrace(this, this.constructor) + this.name = 'AuthenticationError' + this.message = message + this.status = status || 401 } } -export default AuthenticationError; +export default AuthenticationError diff --git a/src/index.ts b/src/index.ts index 8ce7c37e..fd8259c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ -import { Authenticator } from './Authenticator'; -import './type-extensions'; // necessary to make sure that the fastify types are augmented -const passport = new Authenticator(); +import { Authenticator } from './Authenticator' +import './type-extensions' // necessary to make sure that the fastify types are augmented +const passport = new Authenticator() // Workaround for importing fastify-passport in native ESM context -module.exports = exports = passport; -export default passport; -export { Strategy } from './strategies'; -export { Authenticator } from './Authenticator'; +module.exports = exports = passport +export default passport +export { Strategy } from './strategies' +export { Authenticator } from './Authenticator' diff --git a/src/session-managers/SecureSessionManager.ts b/src/session-managers/SecureSessionManager.ts index 17f630fa..d66a85d9 100644 --- a/src/session-managers/SecureSessionManager.ts +++ b/src/session-managers/SecureSessionManager.ts @@ -1,80 +1,80 @@ -import { FastifyRequest } from 'fastify'; -import { AuthenticateOptions } from '../AuthenticationRoute'; -import { SerializeFunction } from '../Authenticator'; -import { FastifySessionObject } from '@fastify/session'; -import { Session, SessionData } from '@fastify/secure-session'; +import { FastifyRequest } from 'fastify' +import { AuthenticateOptions } from '../AuthenticationRoute' +import { SerializeFunction } from '../Authenticator' +import { FastifySessionObject } from '@fastify/session' +import { Session, SessionData } from '@fastify/secure-session' -type Request = FastifyRequest & { session: FastifySessionObject | Session }; +type Request = FastifyRequest & { session: FastifySessionObject | Session } /** Class for storing passport data in the session using `@fastify/secure-session` or `@fastify/session` */ export class SecureSessionManager { - key: string; - clearSessionOnLogin: boolean; - clearSessionIgnoreFields: string[] = ['session']; - serializeUser: SerializeFunction; + key: string + clearSessionOnLogin: boolean + clearSessionIgnoreFields: string[] = ['session'] + serializeUser: SerializeFunction - constructor (serializeUser: SerializeFunction); + constructor (serializeUser: SerializeFunction) constructor ( options: { key?: string; clearSessionOnLogin?: boolean; clearSessionIgnoreFields?: string[] }, serializeUser: SerializeFunction - ); + ) constructor ( options: SerializeFunction | { key?: string; clearSessionOnLogin?: boolean; clearSessionIgnoreFields?: string[] }, serializeUser?: SerializeFunction ) { if (typeof options === 'function') { - this.serializeUser = options; - this.key = 'passport'; - this.clearSessionOnLogin = true; + this.serializeUser = options + this.key = 'passport' + this.clearSessionOnLogin = true } else if (typeof serializeUser === 'function') { - this.serializeUser = serializeUser; + this.serializeUser = serializeUser this.key = - (options && typeof options === 'object' && typeof options.key === 'string' && options.key) || 'passport'; - this.clearSessionOnLogin = options.clearSessionOnLogin ?? true; - this.clearSessionIgnoreFields = [...this.clearSessionIgnoreFields, ...(options.clearSessionIgnoreFields || [])]; + (options && typeof options === 'object' && typeof options.key === 'string' && options.key) || 'passport' + this.clearSessionOnLogin = options.clearSessionOnLogin ?? true + this.clearSessionIgnoreFields = [...this.clearSessionIgnoreFields, ...(options.clearSessionIgnoreFields || [])] } else { - throw new Error('SecureSessionManager#constructor must have a valid serializeUser-function passed as a parameter'); + throw new Error('SecureSessionManager#constructor must have a valid serializeUser-function passed as a parameter') } } async logIn (request: Request, user: any, options?: AuthenticateOptions) { - const object = await this.serializeUser(user, request); + const object = await this.serializeUser(user, request) // Handle @fastify/session to prevent token/CSRF fixation if (request.session.regenerate) { if (this.clearSessionOnLogin && object) { - const keepSessionInfoKeys: string[] = [...this.clearSessionIgnoreFields]; + const keepSessionInfoKeys: string[] = [...this.clearSessionIgnoreFields] if (options?.keepSessionInfo) { - keepSessionInfoKeys.push(...Object.keys(request.session)); + keepSessionInfoKeys.push(...Object.keys(request.session)) } - await request.session.regenerate(keepSessionInfoKeys); + await request.session.regenerate(keepSessionInfoKeys) } else { - await request.session.regenerate(); + await request.session.regenerate() } // Handle @fastify/secure-session against CSRF fixation // TODO: This is quite hacky. The best option would be having a regenerate method // on secure-session as well } else if (this.clearSessionOnLogin && object) { - const currentData: SessionData = request.session?.data() ?? {}; + const currentData: SessionData = request.session?.data() ?? {} for (const field of Object.keys(currentData)) { if (options?.keepSessionInfo || this.clearSessionIgnoreFields.includes(field)) { - continue; + continue } - request.session.set(field, undefined); + request.session.set(field, undefined) } } - request.session.set(this.key, object); + request.session.set(this.key, object) } async logOut (request: Request) { - request.session.set(this.key, undefined); + request.session.set(this.key, undefined) if (request.session.regenerate) { - await request.session.regenerate(); + await request.session.regenerate() } } getUserFromSession (request: Request) { - return request.session.get(this.key); + return request.session.get(this.key) } } diff --git a/src/strategies/SessionStrategy.ts b/src/strategies/SessionStrategy.ts index 165f7c95..2cbc4e6a 100644 --- a/src/strategies/SessionStrategy.ts +++ b/src/strategies/SessionStrategy.ts @@ -1,23 +1,23 @@ -import { Strategy } from './base'; -import { DeserializeFunction } from '../Authenticator'; -import type { FastifyRequest } from 'fastify'; +import { Strategy } from './base' +import { DeserializeFunction } from '../Authenticator' +import type { FastifyRequest } from 'fastify' /** * Default strategy that authenticates already-authenticated requests by retrieving their auth information from the Fastify session. * */ export class SessionStrategy extends Strategy { - private deserializeUser: DeserializeFunction; + private deserializeUser: DeserializeFunction - constructor (deserializeUser: DeserializeFunction); - constructor (options: any, deserializeUser: DeserializeFunction); + constructor (deserializeUser: DeserializeFunction) + constructor (options: any, deserializeUser: DeserializeFunction) constructor (options: any, deserializeUser?: DeserializeFunction) { - super('session'); + super('session') if (typeof options === 'function') { - this.deserializeUser = options; + this.deserializeUser = options } else if (typeof deserializeUser === 'function') { - this.deserializeUser = deserializeUser; + this.deserializeUser = deserializeUser } else { - throw new Error('SessionStrategy#constructor must have a valid deserializeUser-function passed as a parameter'); + throw new Error('SessionStrategy#constructor must have a valid deserializeUser-function passed as a parameter') } } @@ -34,29 +34,29 @@ export class SessionStrategy extends Strategy { */ authenticate (request: FastifyRequest, options?: { pauseStream?: boolean }) { if (!request.passport) { - return this.error(new Error('passport.initialize() plugin not in use')); + return this.error(new Error('passport.initialize() plugin not in use')) } - options = options || {}; + options = options || {} // we need this to prevent basic passport's strategies to use unsupported feature. if (options.pauseStream) { - return this.error(new Error("fastify-passport doesn't support pauseStream option.")); + return this.error(new Error("fastify-passport doesn't support pauseStream option.")) } - const sessionUser = request.passport.sessionManager.getUserFromSession(request); + const sessionUser = request.passport.sessionManager.getUserFromSession(request) if (sessionUser || sessionUser === 0) { this.deserializeUser(sessionUser, request) .catch((err: Error) => this.error(err)) .then(async (user?: any) => { if (!user) { - await request.passport.sessionManager.logOut(request); + await request.passport.sessionManager.logOut(request) } else { - request[request.passport.userProperty] = user; + request[request.passport.userProperty] = user } - this.pass(); - }); + this.pass() + }) } else { - this.pass(); + this.pass() } } } diff --git a/src/strategies/base.ts b/src/strategies/base.ts index d5315a45..8a706155 100644 --- a/src/strategies/base.ts +++ b/src/strategies/base.ts @@ -1,10 +1,10 @@ -import { FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify' export class Strategy { - name: string; + name: string constructor (name: string) { - this.name = name; + this.name = name } /** @@ -17,9 +17,9 @@ export class Strategy { * @param {Object} [options] Strategy-specific options. * @api public */ - authenticate (request: FastifyRequest, options?: any): void | Promise; + authenticate (request: FastifyRequest, options?: any): void | Promise authenticate () { - throw new Error('Strategy#authenticate must be overridden by subclass'); + throw new Error('Strategy#authenticate must be overridden by subclass') } // @@ -41,7 +41,7 @@ export class Strategy { * @param {Object} info * @api public */ - success!: (user: any, info?: any) => void; + success!: (user: any, info?: any) => void /** * Fail authentication, with optional `challenge` and `status`, defaulting @@ -53,7 +53,7 @@ export class Strategy { * @param {Number} status * @api public */ - fail!: ((challenge?: any, status?: number) => void) & ((status?: number) => void); + fail!: ((challenge?: any, status?: number) => void) & ((status?: number) => void) /** * Redirect to `url` with optional `status`, defaulting to 302. @@ -65,7 +65,7 @@ export class Strategy { * @param {Number} status * @api public */ - redirect!: (url: string, status?: number) => void; + redirect!: (url: string, status?: number) => void /** * Pass without making a success or fail decision. @@ -76,7 +76,7 @@ export class Strategy { * * @api public */ - pass!: () => void; + pass!: () => void /** * Internal error while performing authentication. @@ -88,5 +88,5 @@ export class Strategy { * @param {Error} err * @api public */ - error!: (err: Error) => void; + error!: (err: Error) => void } diff --git a/src/strategies/index.ts b/src/strategies/index.ts index e3f59e92..4de6f23a 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -1,6 +1,6 @@ -import type { Strategy as ExpressStrategy } from 'passport'; -import type { Strategy } from './base'; -export * from './base'; -export * from './SessionStrategy'; +import type { Strategy as ExpressStrategy } from 'passport' +import type { Strategy } from './base' +export * from './base' +export * from './SessionStrategy' -export type AnyStrategy = Strategy | ExpressStrategy; +export type AnyStrategy = Strategy | ExpressStrategy diff --git a/src/type-extensions.ts b/src/type-extensions.ts index a30aa865..4859c44b 100644 --- a/src/type-extensions.ts +++ b/src/type-extensions.ts @@ -1,6 +1,6 @@ -import { flashFactory } from '@fastify/flash/lib/flash'; -import { logIn, logOut, isAuthenticated, isUnauthenticated } from './decorators'; -import Authenticator from './Authenticator'; +import { flashFactory } from '@fastify/flash/lib/flash' +import { logIn, logOut, isAuthenticated, isUnauthenticated } from './decorators' +import Authenticator from './Authenticator' declare module 'fastify' { /** diff --git a/test/authorize.test.ts b/test/authorize.test.ts index 0171fc5c..141f7997 100644 --- a/test/authorize.test.ts +++ b/test/authorize.test.ts @@ -1,13 +1,13 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import { RouteHandlerMethod } from 'fastify'; -import { expectType } from 'tsd'; -import { Strategy } from '../src/strategies'; -import { generateTestUser, getConfiguredTestServer } from './helpers'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import { RouteHandlerMethod } from 'fastify' +import { expectType } from 'tsd' +import { Strategy } from '../src/strategies' +import { generateTestUser, getConfiguredTestServer } from './helpers' export class TestThirdPartyStrategy extends Strategy { authenticate (_request: any, _options?: { pauseStream?: boolean }) { - return this.success(generateTestUser()); + return this.success(generateTestUser()) } } @@ -15,25 +15,25 @@ const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { describe('.authorize', () => { test('should return 401 Unauthorized if not logged in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); - fastifyPassport.use(new TestThirdPartyStrategy('third-party')); - expectType(fastifyPassport.authorize('third-party')); + const { server, fastifyPassport } = getConfiguredTestServer() + fastifyPassport.use(new TestThirdPartyStrategy('third-party')) + expectType(fastifyPassport.authorize('third-party')) server.get('/', { preValidation: fastifyPassport.authorize('third-party') }, async (request) => { - const user = request.user as any; - assert.ifError(user); - const account = request.account as any; - assert.ok(account.id); - assert.strictEqual(account.name, 'test'); + const user = request.user as any + assert.ifError(user) + const account = request.account as any + assert.ok(account.id) + assert.strictEqual(account.name, 'test') - return 'it worked'; - }); + return 'it worked' + }) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 200); - }); - }); - }); -}; + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 200) + }) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/csrf-fixation.test.ts b/test/csrf-fixation.test.ts index 76078e65..a5c30a8b 100644 --- a/test/csrf-fixation.test.ts +++ b/test/csrf-fixation.test.ts @@ -1,63 +1,63 @@ -import { test, describe, beforeEach } from 'node:test'; -import assert from 'node:assert'; -import { getConfiguredTestServer, TestBrowserSession } from './helpers'; -import fastifyCsrfProtection from '@fastify/csrf-protection'; +import { test, describe, beforeEach } from 'node:test' +import assert from 'node:assert' +import { getConfiguredTestServer, TestBrowserSession } from './helpers' +import fastifyCsrfProtection from '@fastify/csrf-protection' function createServer (sessionPluginName: '@fastify/session' | '@fastify/secure-session') { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() - server.register(fastifyCsrfProtection, { sessionPlugin: sessionPluginName }); + server.register(fastifyCsrfProtection, { sessionPlugin: sessionPluginName }) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'success' - ); + ) server.get('/csrf', async (_req, reply) => { - return reply.generateCsrf(); - }); + return reply.generateCsrf() + }) server.get('/session', async (req) => { - return req.session.get('_csrf'); - }); - return server; + return req.session.get('_csrf') + }) + return server } const testSuite = (sessionPluginName: '@fastify/session' | '@fastify/secure-session') => { - process.env.SESSION_PLUGIN = sessionPluginName; - const server = createServer(sessionPluginName); + process.env.SESSION_PLUGIN = sessionPluginName + const server = createServer(sessionPluginName) describe(`${sessionPluginName} tests`, () => { describe('guard against fixation', () => { - let user: TestBrowserSession; + let user: TestBrowserSession beforeEach(() => { - user = new TestBrowserSession(server); - }); + user = new TestBrowserSession(server) + }) test('should renegerate csrf token on login', async () => { { - const sess = await user.inject({ method: 'GET', url: '/session' }); - assert.equal(sess.body, ''); + const sess = await user.inject({ method: 'GET', url: '/session' }) + assert.equal(sess.body, '') } - await user.inject({ method: 'GET', url: '/csrf' }); + await user.inject({ method: 'GET', url: '/csrf' }) { - const sess = await user.inject({ method: 'GET', url: '/session' }); - assert.notEqual(sess.body, ''); + const sess = await user.inject({ method: 'GET', url: '/session' }) + assert.notEqual(sess.body, '') } await user.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) { - const sess = await user.inject({ method: 'GET', url: '/session' }); - assert.equal(sess.body, ''); + const sess = await user.inject({ method: 'GET', url: '/session' }) + assert.equal(sess.body, '') } - }); - }); - }); - delete process.env.SESSION_PLUGIN; -}; - -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); + }) + }) + }) + delete process.env.SESSION_PLUGIN +} + +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/decorators.test.ts b/test/decorators.test.ts index 39a83933..9b252605 100644 --- a/test/decorators.test.ts +++ b/test/decorators.test.ts @@ -1,31 +1,31 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import { getConfiguredTestServer, TestStrategy } from './helpers'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import { getConfiguredTestServer, TestStrategy } from './helpers' const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { - const sessionOnlyTest = sessionPluginName === '@fastify/session' ? test : test.skip; - const secureSessionOnlyTest = sessionPluginName === '@fastify/secure-session' ? test : test.skip; + const sessionOnlyTest = sessionPluginName === '@fastify/session' ? test : test.skip + const secureSessionOnlyTest = sessionPluginName === '@fastify/secure-session' ? test : test.skip describe('Request decorators', () => { test('logIn allows logging in an arbitrary user', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) => (request.user as any).name - ); + ) server.post('/force-login', async (request, reply) => { - await request.logIn({ name: 'force logged in user' }); - reply.send('logged in'); - }); + await request.logIn({ name: 'force logged in user' }) + reply.send('logged in') + }) const login = await server.inject({ method: 'POST', url: '/force-login' - }); + }) - assert.strictEqual(login.statusCode, 200); + assert.strictEqual(login.statusCode, 200) const response = await server.inject({ url: '/', @@ -33,31 +33,31 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(login.statusCode, 200); - assert.strictEqual(response.body, 'force logged in user'); - }); + assert.strictEqual(login.statusCode, 200) + assert.strictEqual(response.body, 'force logged in user') + }) secureSessionOnlyTest( 'logIn allows logging in an arbitrary user for the duration of the request if session=false', async () => { - const { server } = getConfiguredTestServer(); + const { server } = getConfiguredTestServer() server.post('/force-login', async (request, reply) => { - await request.logIn({ name: 'force logged in user' }, { session: false }); - reply.send((request.user as any).name); - }); + await request.logIn({ name: 'force logged in user' }, { session: false }) + reply.send((request.user as any).name) + }) const login = await server.inject({ method: 'POST', url: '/force-login' - }); + }) - assert.strictEqual(login.statusCode, 200); - assert.strictEqual(login.body, 'force logged in user'); - assert.strictEqual(login.headers['set-cookie'], undefined); // no user added to session + assert.strictEqual(login.statusCode, 200) + assert.strictEqual(login.body, 'force logged in user') + assert.strictEqual(login.headers['set-cookie'], undefined) // no user added to session } - ); + ) sessionOnlyTest( 'logIn allows logging in an arbitrary user for the duration of the request if session=false', @@ -66,52 +66,52 @@ const testSuite = (sessionPluginName: string) => { secret: 'a secret with minimum length of 32 characters', cookie: { secure: false }, saveUninitialized: false - }; - const { server } = getConfiguredTestServer('test', new TestStrategy('test'), sessionOptions); + } + const { server } = getConfiguredTestServer('test', new TestStrategy('test'), sessionOptions) server.post('/force-login', async (request, reply) => { - await request.logIn({ name: 'force logged in user' }, { session: false }); - reply.send((request.user as any).name); - }); + await request.logIn({ name: 'force logged in user' }, { session: false }) + reply.send((request.user as any).name) + }) const login = await server.inject({ method: 'POST', url: '/force-login' - }); + }) - assert.strictEqual(login.statusCode, 200); - assert.strictEqual(login.body, 'force logged in user'); - assert.strictEqual(login.headers['set-cookie'], undefined); // no user added to session + assert.strictEqual(login.statusCode, 200) + assert.strictEqual(login.body, 'force logged in user') + assert.strictEqual(login.headers['set-cookie'], undefined) // no user added to session } - ); + ) test('should logout', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'the root!' - ); + ) server.get( '/logout', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => { - request.logout(); - reply.send('logged out'); + request.logout() + reply.send('logged out') } - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: false }) }, async () => '' - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const logout = await server.inject({ url: '/logout', @@ -119,10 +119,10 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(logout.statusCode, 200); - assert.ok(logout.headers['set-cookie']); + assert.strictEqual(logout.statusCode, 200) + assert.ok(logout.headers['set-cookie']) const retry = await server.inject({ url: '/', @@ -130,13 +130,13 @@ const testSuite = (sessionPluginName: string) => { cookie: logout.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(retry.statusCode, 401); - }); - }); - }); -}; + assert.strictEqual(retry.statusCode, 401) + }) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/esm/default-esm-export.mjs b/test/esm/default-esm-export.mjs index fcd65f46..01d3e4a3 100644 --- a/test/esm/default-esm-export.mjs +++ b/test/esm/default-esm-export.mjs @@ -1,3 +1,3 @@ -import passport from '../../dist/src/index.js'; +import passport from '../../dist/src/index.js' -passport.initialize(); +passport.initialize() diff --git a/test/esm/esm.test.ts b/test/esm/esm.test.ts index 89bb5bcb..b88d5ad2 100644 --- a/test/esm/esm.test.ts +++ b/test/esm/esm.test.ts @@ -1,17 +1,17 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import { spawnSync } from 'node:child_process'; -import { join } from 'node:path'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import { spawnSync } from 'node:child_process' +import { join } from 'node:path' describe('Native ESM import', () => { test('should be able to use default export', () => { - const { status } = spawnSync('node', [join(__dirname, '../../../test/esm', 'default-esm-export.mjs')]); - assert.strictEqual(status, 0); - }); + const { status } = spawnSync('node', [join(__dirname, '../../../test/esm', 'default-esm-export.mjs')]) + assert.strictEqual(status, 0) + }) test('should be able to use named export', () => { - const { status } = spawnSync('node', [join(__dirname, '../../../test/esm', 'named-esm-export.mjs')]); + const { status } = spawnSync('node', [join(__dirname, '../../../test/esm', 'named-esm-export.mjs')]) - assert.strictEqual(status, 0); - }); -}); + assert.strictEqual(status, 0) + }) +}) diff --git a/test/esm/named-esm-export.mjs b/test/esm/named-esm-export.mjs index 3a97af4e..067fcca9 100644 --- a/test/esm/named-esm-export.mjs +++ b/test/esm/named-esm-export.mjs @@ -1,4 +1,4 @@ -import { Strategy } from '../../dist/src/index.js'; +import { Strategy } from '../../dist/src/index.js' // eslint-disable-next-line no-unused-vars class MS extends Strategy {} diff --git a/test/helpers.ts b/test/helpers.ts index 1756c90c..c9291fbf 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -1,31 +1,31 @@ -import fs from 'node:fs'; -import { join } from 'node:path'; -import fastify, { FastifyInstance } from 'fastify'; -import fastifySecureSession, { SecureSessionPluginOptions } from '@fastify/secure-session'; -import fastifyCookie from '@fastify/cookie'; -import Authenticator, { AuthenticatorOptions } from '../src/Authenticator'; -import { Strategy } from '../src/strategies'; -import { InjectOptions, Response as LightMyRequestResponse } from 'light-my-request'; -import parseCookies from 'set-cookie-parser'; -import { IncomingMessage } from 'node:http'; -import { FastifyRegisterOptions } from 'fastify/types/register'; -import { fastifySession, FastifySessionOptions } from '@fastify/session'; - -const SecretKey = fs.readFileSync(join(__dirname, '../../test', 'secure.key')); - -let counter = 0; -export const generateTestUser = () => ({ name: 'test', id: String(counter++) }); +import fs from 'node:fs' +import { join } from 'node:path' +import fastify, { FastifyInstance } from 'fastify' +import fastifySecureSession, { SecureSessionPluginOptions } from '@fastify/secure-session' +import fastifyCookie from '@fastify/cookie' +import Authenticator, { AuthenticatorOptions } from '../src/Authenticator' +import { Strategy } from '../src/strategies' +import { InjectOptions, Response as LightMyRequestResponse } from 'light-my-request' +import parseCookies from 'set-cookie-parser' +import { IncomingMessage } from 'node:http' +import { FastifyRegisterOptions } from 'fastify/types/register' +import { fastifySession, FastifySessionOptions } from '@fastify/session' + +const SecretKey = fs.readFileSync(join(__dirname, '../../test', 'secure.key')) + +let counter = 0 +export const generateTestUser = () => ({ name: 'test', id: String(counter++) }) export class TestStrategy extends Strategy { authenticate (request: any, _options?: { pauseStream?: boolean }) { if (request.isAuthenticated()) { - return this.pass(); + return this.pass() } if (request.body && request.body.login === 'test' && request.body.password === 'test') { - return this.success(generateTestUser()); + return this.success(generateTestUser()) } - this.fail(); + this.fail() } } @@ -34,95 +34,95 @@ export class TestDatabaseStrategy extends Strategy { name: string, readonly database: Record = {} ) { - super(name); + super(name) } authenticate (request: any, _options?: { pauseStream?: boolean }) { if (request.isAuthenticated()) { - return this.pass(); + return this.pass() } if (request.body) { const user = Object.values(this.database).find( (user) => user.login === request.body.login && user.password === request.body.password - ); + ) if (user) { - return this.success(user); + return this.success(user) } } - this.fail(); + this.fail() } } /** Class representing a browser in tests */ export class TestBrowserSession { - cookies: Record; + cookies: Record constructor (readonly server: FastifyInstance) { - this.cookies = {}; + this.cookies = {} } async inject (opts: InjectOptions): Promise { - opts.headers || (opts.headers = {}); + opts.headers || (opts.headers = {}) opts.headers.cookie = Object.entries(this.cookies) .map(([key, value]) => `${key}=${value}`) - .join('; '); + .join('; ') - const result = await this.server.inject(opts); + const result = await this.server.inject(opts) if (result.statusCode < 500) { for (const { name, value } of parseCookies(result as unknown as IncomingMessage, { decodeValues: false })) { - this.cookies[name] = value; + this.cookies[name] = value } } - return result; + return result } } -type SessionOptions = FastifyRegisterOptions | null; +type SessionOptions = FastifyRegisterOptions | null const loadSessionPlugins = (server: FastifyInstance, sessionOptions: SessionOptions = null) => { if (process.env.SESSION_PLUGIN === '@fastify/session') { - server.register(fastifyCookie); + server.register(fastifyCookie) const options = >(sessionOptions || { secret: 'a secret with minimum length of 32 characters', cookie: { secure: false } - }); - server.register(fastifySession, options); + }) + server.register(fastifySession, options) } else { server.register( fastifySecureSession, | undefined>(sessionOptions || { key: SecretKey }) - ); + ) } -}; +} /** Create a fastify instance with a few simple setup bits added, but without fastify-passport registered or any strategies set up. */ export const getTestServer = (sessionOptions: SessionOptions = null) => { - const server = fastify(); - loadSessionPlugins(server, sessionOptions); + const server = fastify() + loadSessionPlugins(server, sessionOptions) server.setErrorHandler((error, _request, reply) => { - reply.status(500); - reply.send(error); - }); - return server; -}; + reply.status(500) + reply.send(error) + }) + return server +} /** Create a fastify instance with fastify-passport plugin registered but with no strategies registered yet. */ export const getRegisteredTestServer = ( sessionOptions: SessionOptions = null, passportOptions: AuthenticatorOptions = {} ) => { - const fastifyPassport = new Authenticator(passportOptions); - fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)); - fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)); + const fastifyPassport = new Authenticator(passportOptions) + fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)) + fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)) - const server = getTestServer(sessionOptions); - server.register(fastifyPassport.initialize()); - server.register(fastifyPassport.secureSession()); + const server = getTestServer(sessionOptions) + server.register(fastifyPassport.initialize()) + server.register(fastifyPassport.secureSession()) - return { fastifyPassport, server }; -}; + return { fastifyPassport, server } +} /** Create a fastify instance with fastify-passport plugin registered and the given strategy registered with it. */ export const getConfiguredTestServer = ( @@ -131,7 +131,7 @@ export const getConfiguredTestServer = ( sessionOptions: SessionOptions = null, passportOptions: AuthenticatorOptions = {} ) => { - const { fastifyPassport, server } = getRegisteredTestServer(sessionOptions, passportOptions); - fastifyPassport.use(name, strategy); - return { fastifyPassport, server }; -}; + const { fastifyPassport, server } = getRegisteredTestServer(sessionOptions, passportOptions) + fastifyPassport.use(name, strategy) + return { fastifyPassport, server } +} diff --git a/test/independent-strategy-instances.test.ts b/test/independent-strategy-instances.test.ts index 166c9243..051683d1 100644 --- a/test/independent-strategy-instances.test.ts +++ b/test/independent-strategy-instances.test.ts @@ -1,32 +1,32 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import { Strategy } from '../src/strategies'; -import { TestThirdPartyStrategy } from './authorize.test'; -import { getConfiguredTestServer, getRegisteredTestServer, TestStrategy } from './helpers'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import { Strategy } from '../src/strategies' +import { TestThirdPartyStrategy } from './authorize.test' +import { getConfiguredTestServer, getRegisteredTestServer, TestStrategy } from './helpers' class WelcomeStrategy extends Strategy { authenticate (request: any, _options?: { pauseStream?: boolean }) { if (request.isAuthenticated()) { - return this.pass(); + return this.pass() } if (request.body && request.body.login === 'welcomeuser' && request.body.password === 'test') { - return this.success({ name: 'test' }, { message: 'welcome from strategy' }); + return this.success({ name: 'test' }, { message: 'welcome from strategy' }) } - this.fail(); + this.fail() } } const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { test('should allow passing a specific Strategy instance to an authenticate call', async () => { - const { server, fastifyPassport } = getRegisteredTestServer(null, { clearSessionIgnoreFields: ['messages'] }); + const { server, fastifyPassport } = getRegisteredTestServer(null, { clearSessionIgnoreFields: ['messages'] }) server.get( '/', { preValidation: fastifyPassport.authenticate(new WelcomeStrategy('welcome'), { authInfo: false }) }, async (request) => request.session.get('messages') - ); + ) server.post( '/login', { @@ -37,15 +37,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'welcomeuser', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -53,14 +53,14 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, '["welcome from strategy"]'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, '["welcome from strategy"]') + assert.strictEqual(response.statusCode, 200) + }) test('should allow passing a multiple specific Strategy instances to an authenticate call', async () => { - const { server, fastifyPassport } = getRegisteredTestServer(); + const { server, fastifyPassport } = getRegisteredTestServer() server.get( '/', { @@ -69,7 +69,7 @@ const testSuite = (sessionPluginName: string) => { }) }, async (request) => `messages: ${request.session.get('messages')}` - ); + ) server.post( '/login', { @@ -80,15 +80,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -96,14 +96,14 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, 'messages: undefined'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, 'messages: undefined') + assert.strictEqual(response.statusCode, 200) + }) test('should allow passing a mix of Strategy instances and strategy names', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { @@ -112,7 +112,7 @@ const testSuite = (sessionPluginName: string) => { }) }, async (request) => `messages: ${request.session.get('messages')}` - ); + ) server.post( '/login', { @@ -123,15 +123,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -139,41 +139,41 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, 'messages: undefined'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, 'messages: undefined') + assert.strictEqual(response.statusCode, 200) + }) test('should allow passing specific instances to an authorize call', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authorize(new TestThirdPartyStrategy('third-party')) }, async (request) => { - const user = request.user as any; - assert.ifError(user); - const account = request.account as any; - assert.ok(account.id); - assert.strictEqual(account.name, 'test'); + const user = request.user as any + assert.ifError(user) + const account = request.account as any + assert.ok(account.id) + assert.strictEqual(account.name, 'test') - return 'it worked'; + return 'it worked' } - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 200); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 200) + }) test('Strategy instances used during one authentication shouldn\'t be registered', async () => { - const { fastifyPassport } = getRegisteredTestServer(); + const { fastifyPassport } = getRegisteredTestServer() // build a handler with the welcome strategy - fastifyPassport.authenticate(new WelcomeStrategy('welcome'), { authInfo: false }); - assert.strictEqual(fastifyPassport.strategy('welcome'), undefined); - }); - }); -}; - -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); + fastifyPassport.authenticate(new WelcomeStrategy('welcome'), { authInfo: false }) + assert.strictEqual(fastifyPassport.strategy('welcome'), undefined) + }) + }) +} + +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/multi-instance.test.ts b/test/multi-instance.test.ts index a4d2000c..9d33c312 100644 --- a/test/multi-instance.test.ts +++ b/test/multi-instance.test.ts @@ -1,62 +1,62 @@ -import { test, describe, beforeEach } from 'node:test'; -import assert from 'node:assert'; -import { FastifyInstance } from 'fastify'; -import { Authenticator } from '../src/Authenticator'; -import { Strategy } from '../src/strategies'; -import { getTestServer, TestBrowserSession } from './helpers'; +import { test, describe, beforeEach } from 'node:test' +import assert from 'node:assert' +import { FastifyInstance } from 'fastify' +import { Authenticator } from '../src/Authenticator' +import { Strategy } from '../src/strategies' +import { getTestServer, TestBrowserSession } from './helpers' -let counter: number; -let authenticators: Record; +let counter: number +let authenticators: Record async function TestStrategyModule (instance: FastifyInstance, { namespace, clearSessionOnLogin }) { class TestStrategy extends Strategy { authenticate (request: any, _options?: { pauseStream?: boolean }) { if (request.isAuthenticated()) { - return this.pass(); + return this.pass() } if (request.body && request.body.login === 'test' && request.body.password === 'test') { - return this.success({ namespace, id: String(counter++) }); + return this.success({ namespace, id: String(counter++) }) } - this.fail(); + this.fail() } } - const strategyName = `test-${namespace}`; + const strategyName = `test-${namespace}` const authenticator = new Authenticator({ key: `passport${namespace}`, userProperty: `user${namespace}`, clearSessionOnLogin - }); - authenticator.use(strategyName, new TestStrategy(strategyName)); + }) + authenticator.use(strategyName, new TestStrategy(strategyName)) authenticator.registerUserSerializer(async (user) => { if (user.namespace === namespace) { - return namespace + '-' + JSON.stringify(user); + return namespace + '-' + JSON.stringify(user) } - throw 'pass'; // eslint-disable-line no-throw-literal - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) authenticator.registerUserDeserializer(async (serialized: string) => { if (serialized.startsWith(`${namespace}-`)) { - return JSON.parse(serialized.slice(`${namespace}-`.length)); + return JSON.parse(serialized.slice(`${namespace}-`.length)) } - throw 'pass'; // eslint-disable-line no-throw-literal - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) - await instance.register(authenticator.initialize()); - await instance.register(authenticator.secureSession()); - authenticators[namespace] = authenticator; + await instance.register(authenticator.initialize()) + await instance.register(authenticator.secureSession()) + authenticators[namespace] = authenticator instance.get( `/${namespace}`, { preValidation: authenticator.authenticate(strategyName, { authInfo: false }) }, async () => `hello ${namespace}!` - ); + ) instance.get( `/user/${namespace}`, { preValidation: authenticator.authenticate(strategyName, { authInfo: false }) }, async (request) => JSON.stringify(request[`user${namespace}`]) - ); + ) instance.post( `/login-${namespace}`, @@ -69,76 +69,76 @@ async function TestStrategyModule (instance: FastifyInstance, { namespace, clear () => { } - ); + ) instance.post( `/logout-${namespace}`, { preValidation: authenticator.authenticate(strategyName, { authInfo: false }) }, async (request, reply) => { - await request.logout(); - reply.send('logged out'); + await request.logout() + reply.send('logged out') } - ); + ) } const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { describe('multiple registered instances (clearSessionOnLogin: false)', () => { - let server: FastifyInstance; - let session: TestBrowserSession; + let server: FastifyInstance + let session: TestBrowserSession beforeEach(async () => { - counter = 0; - authenticators = {}; - server = getTestServer(); - session = new TestBrowserSession(server); + counter = 0 + authenticators = {} + server = getTestServer() + session = new TestBrowserSession(server) for (const namespace of ['a', 'b']) { - await server.register(TestStrategyModule, { namespace, clearSessionOnLogin: false }); + await server.register(TestStrategyModule, { namespace, clearSessionOnLogin: false }) } - }); + }) test('logging in with one instance should not log in the other instance', async () => { - let response = await session.inject({ method: 'GET', url: '/a' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); + let response = await session.inject({ method: 'GET', url: '/a' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) - response = await session.inject({ method: 'GET', url: '/b' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); + response = await session.inject({ method: 'GET', url: '/b' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) // login a const loginResponse = await session.inject({ method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/a'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/a') // access protected route response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello a!'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello a!') // access user data response = await session.inject({ method: 'GET', url: '/user/a' - }); - assert.strictEqual(response.statusCode, 200); + }) + assert.strictEqual(response.statusCode, 200) // try to access route protected by other instance response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 401); - }); + }) + assert.strictEqual(response.statusCode, 401) + }) test('simultaneous login should be possible', async () => { // login a @@ -146,37 +146,37 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') // access a protected route response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello a!'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello a!') // access b protected route response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello b!'); - }); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello b!') + }) test('logging out with one instance should not log out the other instance', async () => { // login a @@ -184,43 +184,43 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') // logout a response = await session.inject({ method: 'POST', url: '/logout-a' - }); - assert.strictEqual(response.statusCode, 200); + }) + assert.strictEqual(response.statusCode, 200) // try to access route protected by now logged out instance response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 401); + }) + assert.strictEqual(response.statusCode, 401) // access b protected route which should still be logged in response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello b!'); - }); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello b!') + }) test('user objects from different instances should be different', async () => { // login a @@ -228,95 +228,95 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') response = await session.inject({ method: 'GET', url: '/user/a' - }); - assert.strictEqual(response.statusCode, 200); - const userA = JSON.parse(response.body); + }) + assert.strictEqual(response.statusCode, 200) + const userA = JSON.parse(response.body) response = await session.inject({ method: 'GET', url: '/user/b' - }); - assert.strictEqual(response.statusCode, 200); - const userB = JSON.parse(response.body); + }) + assert.strictEqual(response.statusCode, 200) + const userB = JSON.parse(response.body) - assert.notStrictEqual(userA.id, userB.id); - }); - }); + assert.notStrictEqual(userA.id, userB.id) + }) + }) describe('multiple registered instances (clearSessionOnLogin: true)', () => { - let server: FastifyInstance; - let session: TestBrowserSession; + let server: FastifyInstance + let session: TestBrowserSession beforeEach(async () => { - server = getTestServer(); - session = new TestBrowserSession(server); - authenticators = {}; - counter = 0; + server = getTestServer() + session = new TestBrowserSession(server) + authenticators = {} + counter = 0 for (const namespace of ['a', 'b']) { - await server.register(TestStrategyModule, { namespace, clearSessionOnLogin: true }); + await server.register(TestStrategyModule, { namespace, clearSessionOnLogin: true }) } - }); + }) test('logging in with one instance should not log in the other instance', async () => { - let response = await session.inject({ method: 'GET', url: '/a' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); + let response = await session.inject({ method: 'GET', url: '/a' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) - response = await session.inject({ method: 'GET', url: '/b' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); + response = await session.inject({ method: 'GET', url: '/b' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) // login a const loginResponse = await session.inject({ method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/a'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/a') // access protected route response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello a!'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello a!') // access user data response = await session.inject({ method: 'GET', url: '/user/a' - }); - assert.strictEqual(response.statusCode, 200); + }) + assert.strictEqual(response.statusCode, 200) // try to access route protected by other instance response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 401); - }); + }) + assert.strictEqual(response.statusCode, 401) + }) test('simultaneous login should NOT be possible', async () => { // login a @@ -324,37 +324,37 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') // access a protected route (/a) was invalidated after login /b response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 401); - assert.strictEqual(response.body, 'Unauthorized'); + }) + assert.strictEqual(response.statusCode, 401) + assert.strictEqual(response.body, 'Unauthorized') // access b protected route response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello b!'); - }); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello b!') + }) test('logging out with one instance should log out the other instance', async () => { // login a @@ -362,43 +362,43 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') // logout a response = await session.inject({ method: 'POST', url: '/logout-a' - }); - assert.strictEqual(response.statusCode, 401); + }) + assert.strictEqual(response.statusCode, 401) // try to access route protected by now logged out instance response = await session.inject({ method: 'GET', url: '/a' - }); - assert.strictEqual(response.statusCode, 401); + }) + assert.strictEqual(response.statusCode, 401) // access b protected route which should still be logged in response = await session.inject({ method: 'GET', url: '/b' - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello b!'); - }); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello b!') + }) test('user objects from different instances should be different', async () => { // login a @@ -406,39 +406,39 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login-a', payload: { login: 'test', password: 'test' } - }); - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/a'); + }) + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/a') response = await session.inject({ method: 'GET', url: '/user/a' - }); - assert.strictEqual(response.statusCode, 200); - const userA = JSON.parse(response.body); + }) + assert.strictEqual(response.statusCode, 200) + const userA = JSON.parse(response.body) // login b response = await session.inject({ method: 'POST', url: '/login-b', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(response.statusCode, 302); - assert.strictEqual(response.headers.location, '/b'); + assert.strictEqual(response.statusCode, 302) + assert.strictEqual(response.headers.location, '/b') response = await session.inject({ method: 'GET', url: '/user/b' - }); - assert.strictEqual(response.statusCode, 200); - const userB = JSON.parse(response.body); - - assert.notStrictEqual(userA.id, userB.id); - }); - }); - }); -}; - -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); + }) + assert.strictEqual(response.statusCode, 200) + const userB = JSON.parse(response.body) + + assert.notStrictEqual(userA.id, userB.id) + }) + }) + }) +} + +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/passport.test.ts b/test/passport.test.ts index 14c2a9f3..291d6e72 100644 --- a/test/passport.test.ts +++ b/test/passport.test.ts @@ -1,41 +1,41 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import got from 'got'; -import { AddressInfo } from 'node:net'; -import { AuthenticateOptions } from '../src/AuthenticationRoute'; -import Authenticator from '../src/Authenticator'; -import { Strategy } from '../src/strategies'; -import { getConfiguredTestServer, getRegisteredTestServer, getTestServer, TestStrategy } from './helpers'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import got from 'got' +import { AddressInfo } from 'node:net' +import { AuthenticateOptions } from '../src/AuthenticationRoute' +import Authenticator from '../src/Authenticator' +import { Strategy } from '../src/strategies' +import { getConfiguredTestServer, getRegisteredTestServer, getTestServer, TestStrategy } from './helpers' const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { test('should return 401 Unauthorized if not logged in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello world!' - ); - server.post('/login', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, () => {}); + ) + server.post('/login', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, () => {}) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) + }) test('should allow login, and add successMessage to session upon logged in', async () => { const { server, fastifyPassport } = getConfiguredTestServer('test', new TestStrategy('test'), null, { clearSessionIgnoreFields: ['messages'] - }); + }) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => { - reply.send(request.session.get('messages')); + reply.send(request.session.get('messages')) } - ); + ) server.post( '/login', { @@ -46,16 +46,16 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const loginResponse = await server.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/') const homeResponse = await server.inject({ url: '/', @@ -63,35 +63,35 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(homeResponse.body, '["welcome"]'); - assert.strictEqual(homeResponse.statusCode, 200); - }); + assert.strictEqual(homeResponse.body, '["welcome"]') + assert.strictEqual(homeResponse.statusCode, 200) + }) test('should allow login, and add successMessage to the session from a strategy that sets it', async () => { class WelcomeStrategy extends Strategy { authenticate (request: any, _options?: { pauseStream?: boolean }) { if (request.isAuthenticated()) { - return this.pass(); + return this.pass() } if (request.body && request.body.login === 'welcomeuser' && request.body.password === 'test') { - return this.success({ name: 'test' }, { message: 'welcome from strategy' }); + return this.success({ name: 'test' }, { message: 'welcome from strategy' }) } - this.fail(); + this.fail() } } const { server, fastifyPassport } = getConfiguredTestServer('test', new WelcomeStrategy('test'), null, { clearSessionIgnoreFields: ['messages'] - }); + }) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) => request.session.get('messages') - ); + ) server.post( '/login', { @@ -102,15 +102,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'welcomeuser', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -118,28 +118,28 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, '["welcome from strategy"]'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, '["welcome from strategy"]') + assert.strictEqual(response.statusCode, 200) + }) test('should throw error if pauseStream is being used', async () => { - const fastifyPassport = new Authenticator({ clearSessionIgnoreFields: ['messages'] }); - fastifyPassport.use('test', new TestStrategy('test')); - fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)); - fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)); + const fastifyPassport = new Authenticator({ clearSessionIgnoreFields: ['messages'] }) + fastifyPassport.use('test', new TestStrategy('test')) + fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)) + fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)) - const server = getTestServer(); - server.register(fastifyPassport.initialize()); + const server = getTestServer() + server.register(fastifyPassport.initialize()) server.register( fastifyPassport.secureSession({ pauseStream: true } as AuthenticateOptions) - ); + ) server.get('/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) => request.session.get('messages') - ); + ) server.post( '/login', { @@ -150,32 +150,32 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) let response = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(response.statusCode, 500); + }) + assert.strictEqual(response.statusCode, 500) response = await server.inject({ url: '/', method: 'GET' - }); + }) - assert.strictEqual(response.statusCode, 500); - }); + assert.strictEqual(response.statusCode, 500) + }) test('should execute successFlash if logged in', async () => { const { server, fastifyPassport } = getConfiguredTestServer('test', new TestStrategy('test'), null, { clearSessionIgnoreFields: ['flash'] - }); + }) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => reply.flash('success') - ); + ) server.post( '/login', { @@ -186,15 +186,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -202,19 +202,19 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, '["welcome"]'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, '["welcome"]') + assert.strictEqual(response.statusCode, 200) + }) test('should execute successFlash=true if logged in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => reply.flash('success') - ); + ) server.post( '/login', { @@ -225,15 +225,15 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -241,32 +241,32 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, '[]'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, '[]') + assert.strictEqual(response.statusCode, 200) + }) test('should return 200 if logged in and redirect to the successRedirect from options', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: false }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: String(login.headers.location), @@ -274,14 +274,14 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, 'hello world!'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, 'hello world!') + assert.strictEqual(response.statusCode, 200) + }) test('should return use assignProperty option', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.post( '/login', { @@ -292,43 +292,43 @@ const testSuite = (sessionPluginName: string) => { }) }, (request: any, reply: any) => { - reply.send(request.user); + reply.send(request.user) } - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(JSON.parse(login.body).name, 'test'); - }); + }) + assert.strictEqual(JSON.parse(login.body).name, 'test') + }) test('should redirect to the returnTo set in the session upon login', async () => { const { server, fastifyPassport } = getConfiguredTestServer('test', new TestStrategy('test'), null, { clearSessionIgnoreFields: ['returnTo'] - }); + }) server.addHook('preValidation', async (request, _reply) => { - request.session.set('returnTo', '/success'); - }); + request.session.set('returnTo', '/success') + }) server.get( '/success', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successReturnToOrRedirect: '/', authInfo: false }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/success'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/success') const response = await server.inject({ url: String(login.headers.location), @@ -336,32 +336,32 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello world!'); - }); + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello world!') + }) test('should return 200 if logged in and authInfo is true', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: true }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: true }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -369,38 +369,38 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, 'hello world!'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, 'hello world!') + assert.strictEqual(response.statusCode, 200) + }) test('should return 200 if logged in against a running server', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: true }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: true }) }, () => {} - ); + ) - await server.listen(); - server.server.unref(); + await server.listen() + server.server.unref() - const port = (server.server.address() as AddressInfo).port; + const port = (server.server.address() as AddressInfo).port const login = await got('http://localhost:' + port + '/login', { method: 'POST', json: { login: 'test', password: 'test' }, followRedirect: false - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); - const cookies = login.headers['set-cookie']!; - assert.strictEqual(cookies.length, 1); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') + const cookies = login.headers['set-cookie']! + assert.strictEqual(cookies.length, 1) const home = await got({ url: 'http://localhost:' + port, @@ -408,31 +408,31 @@ const testSuite = (sessionPluginName: string) => { cookie: cookies[0] }, method: 'GET' - }); + }) - assert.strictEqual(home.statusCode, 200); - }); + assert.strictEqual(home.statusCode, 200) + }) test('should execute failureRedirect if failed to log in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { failureRedirect: '/failure', authInfo: false }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test1', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/failure'); - }); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/failure') + }) test('should add failureMessage to session if failed to log in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); - server.get('/', async (request, reply) => reply.send(request.session.get('messages'))); + const { server, fastifyPassport } = getConfiguredTestServer() + server.get('/', async (request, reply) => reply.send(request.session.get('messages'))) server.post( '/login', { @@ -442,33 +442,33 @@ const testSuite = (sessionPluginName: string) => { }) }, async () => 'login page' - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'not-correct', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 401); + }) + assert.strictEqual(login.statusCode, 401) - const headers = {}; + const headers = {} if (login.headers['set-cookie']) { - headers['cookie'] = login.headers['set-cookie']; + headers['cookie'] = login.headers['set-cookie'] } const home = await server.inject({ url: '/', headers, method: 'GET' - }); + }) - assert.strictEqual(home.body, '["try again"]'); - assert.strictEqual(home.statusCode, 200); - }); + assert.strictEqual(home.body, '["try again"]') + assert.strictEqual(home.statusCode, 200) + }) test('should add failureFlash to session if failed to log in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() - server.get('/', async (request, reply) => reply.flash('error')); + server.get('/', async (request, reply) => reply.flash('error')) server.post( '/login', { @@ -478,14 +478,14 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'not-correct', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 401); + }) + assert.strictEqual(login.statusCode, 401) const response = await server.inject({ url: '/', @@ -493,15 +493,15 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, '["try again"]'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, '["try again"]') + assert.strictEqual(response.statusCode, 200) + }) test('should add failureFlash=true to session if failed to log in', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); - server.get('/', async (request, reply) => reply.flash('error')); + const { server, fastifyPassport } = getConfiguredTestServer() + server.get('/', async (request, reply) => reply.flash('error')) server.post( '/login', { @@ -511,55 +511,55 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'not-correct', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 401); + }) + assert.strictEqual(login.statusCode, 401) const response = await server.inject({ url: '/', method: 'GET' - }); + }) - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, '[]'); - }); + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, '[]') + }) test('should return 401 Unauthorized if not logged in when used as a handler', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello world!' - ); - server.post('/login', fastifyPassport.authenticate('test', { authInfo: false, successRedirect: '/' })); + ) + server.post('/login', fastifyPassport.authenticate('test', { authInfo: false, successRedirect: '/' })) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.body, 'Unauthorized'); - assert.strictEqual(response.statusCode, 401); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.body, 'Unauthorized') + assert.strictEqual(response.statusCode, 401) + }) test('should redirect when used as a handler', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: true }) }, async () => 'hello world!' - ); - server.post('/login', fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: true })); + ) + server.post('/login', fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: true })) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 302); - assert.strictEqual(login.headers.location, '/'); + }) + assert.strictEqual(login.statusCode, 302) + assert.strictEqual(login.headers.location, '/') const response = await server.inject({ url: '/', @@ -567,58 +567,58 @@ const testSuite = (sessionPluginName: string) => { cookie: login.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(response.body, 'hello world!'); - assert.strictEqual(response.statusCode, 200); - }); + assert.strictEqual(response.body, 'hello world!') + assert.strictEqual(response.statusCode, 200) + }) test('should not log the user in when passed a callback', async () => { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: true }) }, async () => 'hello world!' - ); + ) server.post( '/login', fastifyPassport.authenticate('test', async (request, reply, err, user) => { - return (user as any).name; + return (user as any).name }) - ); + ) const login = await server.inject({ method: 'POST', payload: { login: 'test', password: 'test' }, url: '/login' - }); - assert.strictEqual(login.statusCode, 200); - assert.strictEqual(login.body, 'test'); + }) + assert.strictEqual(login.statusCode, 200) + assert.strictEqual(login.body, 'test') - const headers: Record = {}; + const headers: Record = {} if (login.headers['set-cookie']) { - headers['cookie'] = login.headers['set-cookie']; + headers['cookie'] = login.headers['set-cookie'] } const response = await server.inject({ url: '/', headers, method: 'GET' - }); + }) - assert.strictEqual(response.statusCode, 401); - }); + assert.strictEqual(response.statusCode, 401) + }) test('should allow registering strategies after creating routes referring to those strategies by name', async () => { - const { server, fastifyPassport } = getRegisteredTestServer(null, { clearSessionIgnoreFields: ['messages'] }); + const { server, fastifyPassport } = getRegisteredTestServer(null, { clearSessionIgnoreFields: ['messages'] }) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => { - reply.send(request.session.get('messages')); + reply.send(request.session.get('messages')) } - ); + ) server.post( '/login', @@ -630,19 +630,19 @@ const testSuite = (sessionPluginName: string) => { }) }, () => {} - ); + ) // register the test strategy late (after the above .authenticate calls) - fastifyPassport.use(new TestStrategy('test')); + fastifyPassport.use(new TestStrategy('test')) const loginResponse = await server.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/') const homeResponse = await server.inject({ url: '/', @@ -650,13 +650,13 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(homeResponse.body, '["welcome"]'); - assert.strictEqual(homeResponse.statusCode, 200); - }); - }); -}; + assert.strictEqual(homeResponse.body, '["welcome"]') + assert.strictEqual(homeResponse.statusCode, 200) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/secure-session-manager.test.ts b/test/secure-session-manager.test.ts index 2377f864..485077a9 100644 --- a/test/secure-session-manager.test.ts +++ b/test/secure-session-manager.test.ts @@ -1,8 +1,8 @@ -import { test, describe, mock } from 'node:test'; -import assert from 'node:assert'; -import { FastifyRequest } from 'fastify'; -import { SerializeFunction } from '../src/Authenticator'; -import { SecureSessionManager } from '../src/session-managers/SecureSessionManager'; +import { test, describe, mock } from 'node:test' +import assert from 'node:assert' +import { FastifyRequest } from 'fastify' +import { SerializeFunction } from '../src/Authenticator' +import { SecureSessionManager } from '../src/session-managers/SecureSessionManager' describe('SecureSessionManager', () => { test('should throw an Error if no parameter was passed', () => { @@ -10,141 +10,141 @@ describe('SecureSessionManager', () => { // @ts-expect-error - strictEqual-error expecting atleast a parameter () => new SecureSessionManager(), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SecureSessionManager#constructor must have a valid serializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should throw an Error if no serializeUser-function was passed as second parameter', () => { assert.throws( // @ts-expect-error - strictEqual-error expecting a function as second parameter () => new SecureSessionManager({}), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SecureSessionManager#constructor must have a valid serializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should throw an Error if no serializeUser-function was passed as second parameter', () => { assert.throws( // @ts-expect-error - strictEqual-error expecting a function as second parameter () => new SecureSessionManager({}), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SecureSessionManager#constructor must have a valid serializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should not throw an Error if no serializeUser-function was passed as first parameter', () => { - const sessionManager = new SecureSessionManager(((id) => id) as unknown as SerializeFunction); - assert.strictEqual(sessionManager.key, 'passport'); - }); + const sessionManager = new SecureSessionManager(((id) => id) as unknown as SerializeFunction) + assert.strictEqual(sessionManager.key, 'passport') + }) test('should not throw an Error if no serializeUser-function was passed as second parameter', () => { - const sessionManager = new SecureSessionManager({}, ((id) => id) as unknown as SerializeFunction); - assert.strictEqual(sessionManager.key, 'passport'); - }); + const sessionManager = new SecureSessionManager({}, ((id) => id) as unknown as SerializeFunction) + assert.strictEqual(sessionManager.key, 'passport') + }) test('should set the key accordingly', () => { - const sessionManager = new SecureSessionManager({ key: 'test' }, ((id) => id) as unknown as SerializeFunction); - assert.strictEqual(sessionManager.key, 'test'); - }); + const sessionManager = new SecureSessionManager({ key: 'test' }, ((id) => id) as unknown as SerializeFunction) + assert.strictEqual(sessionManager.key, 'test') + }) test('should ignore non-string keys', () => { // @ts-expect-error - strictEqual-error key has to be of type string - const sessionManager = new SecureSessionManager({ key: 1 }, ((id) => id) as unknown as SerializeFunction); - assert.strictEqual(sessionManager.key, 'passport'); - }); + const sessionManager = new SecureSessionManager({ key: 1 }, ((id) => id) as unknown as SerializeFunction) + assert.strictEqual(sessionManager.key, 'passport') + }) test('should only call request.session.regenerate once if a function', async () => { - const sessionManger = new SecureSessionManager({}, ((id) => id) as unknown as SerializeFunction); - const user = { id: 'test' }; + const sessionManger = new SecureSessionManager({}, ((id) => id) as unknown as SerializeFunction) + const user = { id: 'test' } const request = { session: { regenerate: mock.fn(() => {}), set: () => {}, data: () => {} } - } as unknown as FastifyRequest; - await sessionManger.logIn(request, user); + } as unknown as FastifyRequest + await sessionManger.logIn(request, user) // @ts-expect-error - regenerate is a mock function - assert.strictEqual(request.session.regenerate.mock.callCount(), 1); - }); + assert.strictEqual(request.session.regenerate.mock.callCount(), 1) + }) test('should call request.session.regenerate function if clearSessionOnLogin is false', async () => { const sessionManger = new SecureSessionManager( { clearSessionOnLogin: false }, ((id) => id) as unknown as SerializeFunction - ); - const user = { id: 'test' }; + ) + const user = { id: 'test' } const request = { session: { regenerate: mock.fn(() => {}), set: () => {}, data: () => {} } - } as unknown as FastifyRequest; - await sessionManger.logIn(request, user); + } as unknown as FastifyRequest + await sessionManger.logIn(request, user) // @ts-expect-error - regenerate is a mock function - assert.strictEqual(request.session.regenerate.mock.callCount(), 1); - mock.reset(); - }); + assert.strictEqual(request.session.regenerate.mock.callCount(), 1) + mock.reset() + }) test('should call request.session.regenerate function with all properties from session if keepSessionInfo is true', async () => { const sessionManger = new SecureSessionManager( { clearSessionOnLogin: true }, ((id) => id) as unknown as SerializeFunction - ); - const user = { id: 'test' }; + ) + const user = { id: 'test' } const request = { session: { regenerate: mock.fn(() => {}), set: () => {}, data: () => {}, sessionValue: 'exist' } - } as unknown as FastifyRequest; - await sessionManger.logIn(request, user, { keepSessionInfo: true }); + } as unknown as FastifyRequest + await sessionManger.logIn(request, user, { keepSessionInfo: true }) // @ts-expect-error - regenerate is a mock function - assert.strictEqual(request.session.regenerate.mock.callCount(), 1); + assert.strictEqual(request.session.regenerate.mock.callCount(), 1) // @ts-expect-error - regenerate is a mock function assert.deepStrictEqual(request.session.regenerate.mock.calls[0].arguments, [ ['session', 'regenerate', 'set', 'data', 'sessionValue'] - ]); - mock.reset(); - }); + ]) + mock.reset() + }) test('should call request.session.regenerate function with default properties from session if keepSessionInfo is false', async () => { const sessionManger = new SecureSessionManager( { clearSessionOnLogin: true }, ((id) => id) as unknown as SerializeFunction - ); - const user = { id: 'test' }; + ) + const user = { id: 'test' } const request = { session: { regenerate: mock.fn(() => {}), set: () => {}, data: () => {}, sessionValue: 'exist' } - } as unknown as FastifyRequest; - await sessionManger.logIn(request, user, { keepSessionInfo: false }); + } as unknown as FastifyRequest + await sessionManger.logIn(request, user, { keepSessionInfo: false }) // @ts-expect-error - regenerate is a mock function - assert.strictEqual(request.session.regenerate.mock.callCount(), 1); + assert.strictEqual(request.session.regenerate.mock.callCount(), 1) // @ts-expect-error - regenerate is a mock function - assert.deepStrictEqual(request.session.regenerate.mock.calls[0].arguments, [['session']]); - }); + assert.deepStrictEqual(request.session.regenerate.mock.calls[0].arguments, [['session']]) + }) test('should call session.set function if no regenerate function provided and keepSessionInfo is true', async () => { const sessionManger = new SecureSessionManager( { clearSessionOnLogin: true }, ((id) => id) as unknown as SerializeFunction - ); - const user = { id: 'test' }; - const set = mock.fn(); + ) + const user = { id: 'test' } + const set = mock.fn() const request = { session: { set, data: () => {}, sessionValue: 'exist' } - } as unknown as FastifyRequest; - await sessionManger.logIn(request, user, { keepSessionInfo: false }); - assert.strictEqual(set.mock.callCount(), 1); - assert.deepStrictEqual(set.mock.calls[0].arguments, ['passport', { id: 'test' }]); - }); -}); + } as unknown as FastifyRequest + await sessionManger.logIn(request, user, { keepSessionInfo: false }) + assert.strictEqual(set.mock.callCount(), 1) + assert.deepStrictEqual(set.mock.calls[0].arguments, ['passport', { id: 'test' }]) + }) +}) diff --git a/test/session-isolation.test.ts b/test/session-isolation.test.ts index e2dffb23..430f916d 100644 --- a/test/session-isolation.test.ts +++ b/test/session-isolation.test.ts @@ -1,100 +1,100 @@ -import { test, describe, beforeEach } from 'node:test'; -import assert from 'node:assert'; -import { generateTestUser, getConfiguredTestServer, TestBrowserSession } from './helpers'; +import { test, describe, beforeEach } from 'node:test' +import assert from 'node:assert' +import { generateTestUser, getConfiguredTestServer, TestBrowserSession } from './helpers' function createServer () { - const { server, fastifyPassport } = getConfiguredTestServer(); + const { server, fastifyPassport } = getConfiguredTestServer() server.get( '/protected', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello!' - ); + ) server.get('/my-id', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) => String((request.user as any).id) - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'success' - ); + ) server.post('/force-login', async (request, reply) => { - await request.logIn(generateTestUser()); - reply.send('logged in'); - }); + await request.logIn(generateTestUser()) + reply.send('logged in') + }) server.post( '/logout', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request, reply) => { - await request.logout(); - reply.send('logged out'); + await request.logout() + reply.send('logged out') } - ); - return server; + ) + return server } const testSuite = (sessionPluginName: string) => { - process.env.SESSION_PLUGIN = sessionPluginName; - const server = createServer(); + process.env.SESSION_PLUGIN = sessionPluginName + const server = createServer() describe(`${sessionPluginName} tests`, () => { - const sessionOnlyTest = sessionPluginName === '@fastify/session' ? test : test.skip; + const sessionOnlyTest = sessionPluginName === '@fastify/session' ? test : test.skip describe('session isolation', () => { - let userA, userB, userC; + let userA, userB, userC beforeEach(() => { - userA = new TestBrowserSession(server); - userB = new TestBrowserSession(server); - userC = new TestBrowserSession(server); - }); + userA = new TestBrowserSession(server) + userB = new TestBrowserSession(server) + userC = new TestBrowserSession(server) + }) test('should return 401 Unauthorized if not logged in', async () => { await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 401); + const response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 401) }) - ); + ) await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 401); + const response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 401) }) - ); - }); + ) + }) test('logging in one user shouldn\'t log in the others', async () => { await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 401); + const response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 401) }) - ); + ) let response = await userA.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'success'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'success') - response = await userA.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello!'); + response = await userA.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello!') await Promise.all( [userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 401); + const response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 401) }) - ); + ) - response = await userA.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello!'); - }); + response = await userA.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello!') + }) test('logging in each user should keep their sessions independent', async () => { await Promise.all( @@ -103,27 +103,27 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'success'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'success') - response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello!'); + response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello!') }) - ); + ) const ids = await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/my-id' }); - assert.strictEqual(response.statusCode, 200); - return response.body; + const response = await user.inject({ method: 'GET', url: '/my-id' }) + assert.strictEqual(response.statusCode, 200) + return response.body }) - ); + ) // assert.deepStrictEqual each returned ID to be unique - assert.deepStrictEqual(Array.from(new Set(ids)).sort(), ids.sort()); - }); + assert.deepStrictEqual(Array.from(new Set(ids)).sort(), ids.sort()) + }) test('logging out one user shouldn\'t log out the others', async () => { await Promise.all( @@ -132,73 +132,73 @@ const testSuite = (sessionPluginName: string) => { method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'success'); + }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'success') - response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello!'); + response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello!') }) - ); + ) let response = await userB.inject({ url: '/logout', method: 'POST' - }); - assert.strictEqual(response.statusCode, 200); + }) + assert.strictEqual(response.statusCode, 200) response = await userB.inject({ url: '/protected', method: 'GET' - }); - assert.strictEqual(response.statusCode, 401); + }) + assert.strictEqual(response.statusCode, 401) await Promise.all( [userA, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/protected' }); - assert.strictEqual(response.statusCode, 200); - assert.strictEqual(response.body, 'hello!'); + const response = await user.inject({ method: 'GET', url: '/protected' }) + assert.strictEqual(response.statusCode, 200) + assert.strictEqual(response.body, 'hello!') }) - ); - }); + ) + }) test('force logging in users shouldn\'t change the login state of the others', async () => { await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'POST', url: '/force-login' }); - assert.strictEqual(response.statusCode, 200); + const response = await user.inject({ method: 'POST', url: '/force-login' }) + assert.strictEqual(response.statusCode, 200) }) - ); + ) const ids = await Promise.all( [userA, userB, userC].map(async (user) => { - const response = await user.inject({ method: 'GET', url: '/my-id' }); - assert.strictEqual(response.statusCode, 200); - return response.body; + const response = await user.inject({ method: 'GET', url: '/my-id' }) + assert.strictEqual(response.statusCode, 200) + return response.body }) - ); + ) // assert.deepStrictEqual each returned ID to be unique - assert.deepStrictEqual(Array.from(new Set(ids)).sort(), ids.sort()); - }); + assert.deepStrictEqual(Array.from(new Set(ids)).sort(), ids.sort()) + }) sessionOnlyTest('should regenerate session on login', async () => { - assert.strictEqual(userA.cookies['sessionId'], undefined); - await userA.inject({ method: 'GET', url: '/protected' }); - assert.ok(userA.cookies['sessionId']); - const prevSessionId = userA.cookies.sessionId; + assert.strictEqual(userA.cookies['sessionId'], undefined) + await userA.inject({ method: 'GET', url: '/protected' }) + assert.ok(userA.cookies['sessionId']) + const prevSessionId = userA.cookies.sessionId await userA.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); - assert.notStrictEqual(userA.cookies.sessionId, prevSessionId); - }); - }); - }); - delete process.env.SESSION_PLUGIN; -}; - -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); + }) + assert.notStrictEqual(userA.cookies.sessionId, prevSessionId) + }) + }) + }) + delete process.env.SESSION_PLUGIN +} + +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/session-serialization.test.ts b/test/session-serialization.test.ts index 8fbd5506..3d30f763 100644 --- a/test/session-serialization.test.ts +++ b/test/session-serialization.test.ts @@ -1,54 +1,54 @@ -import { test, describe, mock } from 'node:test'; -import assert from 'node:assert'; -import { FastifyInstance } from 'fastify'; -import { FastifyRequest } from 'fastify/types/request'; -import Authenticator from '../src/Authenticator'; -import { getTestServer, TestDatabaseStrategy, TestStrategy } from './helpers'; +import { test, describe, mock } from 'node:test' +import assert from 'node:assert' +import { FastifyInstance } from 'fastify' +import { FastifyRequest } from 'fastify/types/request' +import Authenticator from '../src/Authenticator' +import { getTestServer, TestDatabaseStrategy, TestStrategy } from './helpers' const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { describe('Authenticator session serialization', () => { test('it should roundtrip a user', async () => { - const fastifyPassport = new Authenticator(); + const fastifyPassport = new Authenticator() - fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)); - fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)); + fastifyPassport.registerUserSerializer(async (user) => JSON.stringify(user)) + fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)) - const user = { name: 'foobar' }; - const request = {} as unknown as FastifyRequest; + const user = { name: 'foobar' } + const request = {} as unknown as FastifyRequest assert.deepStrictEqual( await fastifyPassport.deserializeUser(await fastifyPassport.serializeUser(user, request), request), user - ); - }); + ) + }) const setupSerializationTestServer = async (fastifyPassport: Authenticator) => { - const server = getTestServer(); - server.register(fastifyPassport.initialize()); - server.register(fastifyPassport.secureSession()); + const server = getTestServer() + server.register(fastifyPassport.initialize()) + server.register(fastifyPassport.secureSession()) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('test', { successRedirect: '/', authInfo: false }) }, () => {} - ); - server.get('/unprotected', async () => 'some content'); - return server; - }; + ) + server.get('/unprotected', async () => 'some content') + return server + } const verifySuccessfulLogin = async (server: FastifyInstance) => { const loginResponse = await server.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/') const homeResponse = await server.inject({ url: '/', @@ -56,70 +56,70 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(homeResponse.body, 'hello world!'); - assert.strictEqual(homeResponse.statusCode, 200); - }; + assert.strictEqual(homeResponse.body, 'hello world!') + assert.strictEqual(homeResponse.statusCode, 200) + } test('should allow multiple user serializers and deserializers', async () => { - const fastifyPassport = new Authenticator(); - fastifyPassport.use('test', new TestStrategy('test')); + const fastifyPassport = new Authenticator() + fastifyPassport.use('test', new TestStrategy('test')) fastifyPassport.registerUserSerializer(async () => { - throw 'pass'; // eslint-disable-line no-throw-literal - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) fastifyPassport.registerUserSerializer(async () => { - throw 'pass'; // eslint-disable-line no-throw-literal - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) fastifyPassport.registerUserSerializer(async (user) => { - return JSON.stringify(user); - }); + return JSON.stringify(user) + }) fastifyPassport.registerUserDeserializer(async () => { - throw 'pass'; // eslint-disable-line no-throw-literal - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) fastifyPassport.registerUserDeserializer(async () => { - throw 'pass'; // eslint-disable-line no-throw-literal - }); - fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)); - const server = await setupSerializationTestServer(fastifyPassport); - await verifySuccessfulLogin(server); - }); + throw 'pass' // eslint-disable-line no-throw-literal + }) + fastifyPassport.registerUserDeserializer(async (serialized: string) => JSON.parse(serialized)) + const server = await setupSerializationTestServer(fastifyPassport) + await verifySuccessfulLogin(server) + }) test('should allow user serializers/deserializers that work like a database', async () => { - const fastifyPassport = new Authenticator(); - const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }); - fastifyPassport.use('test', strategy); - fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id); - fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized]); + const fastifyPassport = new Authenticator() + const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }) + fastifyPassport.use('test', strategy) + fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id) + fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized]) - const server = await setupSerializationTestServer(fastifyPassport); - await verifySuccessfulLogin(server); - await verifySuccessfulLogin(server); - }); + const server = await setupSerializationTestServer(fastifyPassport) + await verifySuccessfulLogin(server) + await verifySuccessfulLogin(server) + }) test('should throw if user deserializers return undefined', async () => { // jest.spyOn(console, 'error').mockImplementation(jest.fn()) - console.error = mock.fn(); - const fastifyPassport = new Authenticator(); - const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }); - fastifyPassport.use('test', strategy); - fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id); - fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized]); + console.error = mock.fn() + const fastifyPassport = new Authenticator() + const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }) + fastifyPassport.use('test', strategy) + fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id) + fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized]) - const server = await setupSerializationTestServer(fastifyPassport); - await verifySuccessfulLogin(server); + const server = await setupSerializationTestServer(fastifyPassport) + await verifySuccessfulLogin(server) const loginResponse = await server.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/') // user id 1 is logged in now, simulate deleting them from the database while logged in - delete strategy.database['1']; + delete strategy.database['1'] const homeResponse = await server.inject({ url: '/', @@ -127,13 +127,13 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(homeResponse.statusCode, 500); + assert.strictEqual(homeResponse.statusCode, 500) assert.strictEqual( JSON.parse(homeResponse.body)?.message, 'Failed to deserialize user out of session. Tried 1 serializers.' - ); + ) // can't serve other requests either because the secure session decode fails, which would populate request.user even for unauthenticated requests const otherResponse = await server.inject({ @@ -142,36 +142,36 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(otherResponse.statusCode, 500); + assert.strictEqual(otherResponse.statusCode, 500) assert.strictEqual( JSON.parse(otherResponse.body)?.message, 'Failed to deserialize user out of session. Tried 1 serializers.' - ); - }); + ) + }) test('should deny access if user deserializers return null for logged in sessions', async () => { - const fastifyPassport = new Authenticator(); - const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }); - fastifyPassport.use('test', strategy); - fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id); - fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized] || null); + const fastifyPassport = new Authenticator() + const strategy = new TestDatabaseStrategy('test', { 1: { id: '1', login: 'test', password: 'test' } }) + fastifyPassport.use('test', strategy) + fastifyPassport.registerUserSerializer<{ id: string; name: string }, string>(async (user) => user.id) + fastifyPassport.registerUserDeserializer(async (serialized: string) => strategy.database[serialized] || null) - const server = await setupSerializationTestServer(fastifyPassport); - await verifySuccessfulLogin(server); + const server = await setupSerializationTestServer(fastifyPassport) + await verifySuccessfulLogin(server) const loginResponse = await server.inject({ method: 'POST', url: '/login', payload: { login: 'test', password: 'test' } - }); + }) - assert.strictEqual(loginResponse.statusCode, 302); - assert.strictEqual(loginResponse.headers.location, '/'); + assert.strictEqual(loginResponse.statusCode, 302) + assert.strictEqual(loginResponse.headers.location, '/') // user id 1 is logged in now, simulate deleting them from the database while logged in - delete strategy.database['1']; + delete strategy.database['1'] const homeResponse = await server.inject({ url: '/', @@ -179,9 +179,9 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(homeResponse.statusCode, 401); + assert.strictEqual(homeResponse.statusCode, 401) // should still be able to serve unauthenticated requests just fine const otherResponse = await server.inject({ @@ -190,14 +190,14 @@ const testSuite = (sessionPluginName: string) => { cookie: loginResponse.headers['set-cookie'] }, method: 'GET' - }); + }) - assert.strictEqual(otherResponse.statusCode, 200); - assert.strictEqual(otherResponse.body, 'some content'); - }); - }); - }); -}; + assert.strictEqual(otherResponse.statusCode, 200) + assert.strictEqual(otherResponse.body, 'some content') + }) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/session-strategy.test.ts b/test/session-strategy.test.ts index b93ab430..0cbcf4c8 100644 --- a/test/session-strategy.test.ts +++ b/test/session-strategy.test.ts @@ -1,7 +1,7 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import { SerializeFunction } from '../src/Authenticator'; -import { SessionStrategy } from '../src/strategies/SessionStrategy'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import { SerializeFunction } from '../src/Authenticator' +import { SessionStrategy } from '../src/strategies/SessionStrategy' describe('SessionStrategy', () => { test('should throw an Error if no parameter was passed', () => { @@ -9,51 +9,51 @@ describe('SessionStrategy', () => { // @ts-expect-error.strictEqual-error expecting atleast a parameter () => new SessionStrategy(), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SessionStrategy#constructor must have a valid deserializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should throw an Error if no deserializeUser-function was passed as second parameter', () => { assert.throws( // @ts-expect-error.strictEqual-error expecting a function as second parameter () => new SessionStrategy({}), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SessionStrategy#constructor must have a valid deserializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should throw an Error if no deserializeUser-function was passed as second parameter', () => { assert.throws( // @ts-expect-error.strictEqual-error expecting a function as second parameter () => new SessionStrategy({}), (err) => { - assert(err instanceof Error); + assert(err instanceof Error) assert.strictEqual( err.message, 'SessionStrategy#constructor must have a valid deserializeUser-function passed as a parameter' - ); - return true; + ) + return true } - ); - }); + ) + }) test('should not throw an Error if no deserializeUser-function was passed as first parameter', () => { - assert.doesNotThrow(() => new SessionStrategy(((id) => id) as unknown as SerializeFunction)); - }); + assert.doesNotThrow(() => new SessionStrategy(((id) => id) as unknown as SerializeFunction)) + }) test('should not throw an Error if no deserializeUser-function was passed as second parameter', () => { - assert.doesNotThrow(() => new SessionStrategy({}, ((id) => id) as unknown as SerializeFunction)); - }); -}); + assert.doesNotThrow(() => new SessionStrategy({}, ((id) => id) as unknown as SerializeFunction)) + }) +}) diff --git a/test/strategies-integration.test.ts b/test/strategies-integration.test.ts index 619f8836..4400cfa0 100644 --- a/test/strategies-integration.test.ts +++ b/test/strategies-integration.test.ts @@ -1,10 +1,10 @@ -import { test, describe } from 'node:test'; -import assert, { fail } from 'node:assert'; -import { Strategy as FacebookStrategy } from 'passport-facebook'; -import { Strategy as GitHubStrategy } from 'passport-github2'; -import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth'; -import { Issuer as OpenIdIssuer, Strategy as OpenIdStrategy } from 'openid-client'; -import { getConfiguredTestServer, TestStrategy } from './helpers'; +import { test, describe } from 'node:test' +import assert, { fail } from 'node:assert' +import { Strategy as FacebookStrategy } from 'passport-facebook' +import { Strategy as GitHubStrategy } from 'passport-github2' +import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth' +import { Issuer as OpenIdIssuer, Strategy as OpenIdStrategy } from 'openid-client' +import { getConfiguredTestServer, TestStrategy } from './helpers' const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { @@ -16,24 +16,24 @@ const testSuite = (sessionPluginName: string) => { callbackURL: 'http://www.example.com/auth/google/callback' }, () => fail() - ); + ) - const { server, fastifyPassport } = getConfiguredTestServer('google', strategy); + const { server, fastifyPassport } = getConfiguredTestServer('google', strategy) server.get( '/', { preValidation: fastifyPassport.authenticate('google', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('google', { authInfo: false }) }, async () => 'hello' - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 302); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 302) + }) test('should initiate oauth with the facebook strategy from npm', async () => { const strategy: TestStrategy = new FacebookStrategy( @@ -43,24 +43,24 @@ const testSuite = (sessionPluginName: string) => { callbackURL: 'http://www.example.com/auth/facebook/callback' }, () => fail() - ); + ) - const { server, fastifyPassport } = getConfiguredTestServer('facebook', strategy); + const { server, fastifyPassport } = getConfiguredTestServer('facebook', strategy) server.get( '/', { preValidation: fastifyPassport.authenticate('facebook', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('facebook', { authInfo: false }) }, async () => 'hello' - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 302); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 302) + }) test('should initiate oauth with the github strategy from npm', async () => { const strategy: TestStrategy = new GitHubStrategy( @@ -70,59 +70,59 @@ const testSuite = (sessionPluginName: string) => { callbackURL: 'http://www.example.com/auth/facebook/callback' }, () => fail() - ); + ) - const { server, fastifyPassport } = getConfiguredTestServer('github', strategy); + const { server, fastifyPassport } = getConfiguredTestServer('github', strategy) server.get( '/', { preValidation: fastifyPassport.authenticate('github', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('github', { authInfo: false }) }, async () => 'hello' - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 302); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 302) + }) test('should initiate oauth with the openid-client strategy from npm', async () => { - const issuer = new OpenIdIssuer({ issuer: 'test_issuer', authorization_endpoint: 'http://www.example.com' }); + const issuer = new OpenIdIssuer({ issuer: 'test_issuer', authorization_endpoint: 'http://www.example.com' }) const client = new issuer.Client({ client_id: 'identifier', client_secret: 'secure', redirect_uris: ['http://www.example.com/auth/openid-client/callback'] - }); + }) const strategy = new OpenIdStrategy( { client }, () => fail() - ) as TestStrategy; + ) as TestStrategy - const { server, fastifyPassport } = getConfiguredTestServer('openid-client', strategy); + const { server, fastifyPassport } = getConfiguredTestServer('openid-client', strategy) server.get( '/', { preValidation: fastifyPassport.authenticate('openid-client', { authInfo: false }) }, async () => 'hello world!' - ); + ) server.post( '/login', { preValidation: fastifyPassport.authenticate('openid-client', { authInfo: false }) }, async () => 'hello' - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 302); - }); - }); -}; + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 302) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session') diff --git a/test/strategy.test.ts b/test/strategy.test.ts index d96ade19..ad114405 100644 --- a/test/strategy.test.ts +++ b/test/strategy.test.ts @@ -1,91 +1,91 @@ -import { test, describe } from 'node:test'; -import assert from 'node:assert'; -import Authenticator from '../src/Authenticator'; -import { getConfiguredTestServer, TestStrategy } from './helpers'; -import { Strategy } from '../src/strategies'; +import { test, describe } from 'node:test' +import assert from 'node:assert' +import Authenticator from '../src/Authenticator' +import { getConfiguredTestServer, TestStrategy } from './helpers' +import { Strategy } from '../src/strategies' const testSuite = (sessionPluginName: string) => { describe(`${sessionPluginName} tests`, () => { test('should be able to unuse strategy', () => { - const fastifyPassport = new Authenticator(); - const testStrategy = new TestStrategy('test'); - fastifyPassport.use(testStrategy); - fastifyPassport.unuse('test'); - }); + const fastifyPassport = new Authenticator() + const testStrategy = new TestStrategy('test') + fastifyPassport.use(testStrategy) + fastifyPassport.unuse('test') + }) test('should throw error if strategy has no name', () => { - const fastifyPassport = new Authenticator(); + const fastifyPassport = new Authenticator() assert.throws(() => { - fastifyPassport.use({} as Strategy); - }); - }); + fastifyPassport.use({} as Strategy) + }) + }) test('should catch synchronous strategy errors and fail authentication', async () => { class ErrorStrategy extends Strategy { authenticate (_request: any, _options?: { pauseStream?: boolean }) { - throw new Error('the strategy threw an error'); + throw new Error('the strategy threw an error') } } - const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')); - server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!'); + const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')) + server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!') - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 500); - assert.strictEqual(JSON.parse(response.body).message, 'the strategy threw an error'); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 500) + assert.strictEqual(JSON.parse(response.body).message, 'the strategy threw an error') + }) test('should catch asynchronous strategy errors and fail authentication', async () => { class ErrorStrategy extends Strategy { async authenticate (_request: any, _options?: { pauseStream?: boolean }) { - await Promise.resolve(); - throw new Error('the strategy threw an error'); + await Promise.resolve() + throw new Error('the strategy threw an error') } } - const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')); - server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!'); + const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')) + server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!') - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 500); - assert.strictEqual(JSON.parse(response.body).message, 'the strategy threw an error'); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 500) + assert.strictEqual(JSON.parse(response.body).message, 'the strategy threw an error') + }) test('should be able to fail with a failure flash message', async () => { class ErrorStrategy extends Strategy { async authenticate (_request: any, _options?: { pauseStream?: boolean }) { - await Promise.resolve(); - this.fail({ message: 'The strategy failed with an error message' }, 401); + await Promise.resolve() + this.fail({ message: 'The strategy failed with an error message' }, 401) } } - const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')); + const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')) server.get( '/', { preValidation: fastifyPassport.authenticate('test', { failureFlash: true }) }, async () => 'hello world!' - ); + ) - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 401); - }); + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 401) + }) test('should be able to fail without a failure flash message', async () => { class ErrorStrategy extends Strategy { async authenticate (_request: any, _options?: { pauseStream?: boolean }) { - await Promise.resolve(); - this.fail(401); + await Promise.resolve() + this.fail(401) } } - const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')); - server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!'); + const { server, fastifyPassport } = getConfiguredTestServer('test', new ErrorStrategy('test')) + server.get('/', { preValidation: fastifyPassport.authenticate('test') }, async () => 'hello world!') - const response = await server.inject({ method: 'GET', url: '/' }); - assert.strictEqual(response.statusCode, 401); - }); - }); -}; + const response = await server.inject({ method: 'GET', url: '/' }) + assert.strictEqual(response.statusCode, 401) + }) + }) +} -testSuite('@fastify/session'); -testSuite('@fastify/secure-session'); +testSuite('@fastify/session') +testSuite('@fastify/secure-session')