@@ -6,6 +6,7 @@ import fp from "fastify-plugin";
66import jwt , { JwtHeader , SigningKeyCallback } from "jsonwebtoken" ;
77import { JwksClient } from "jwks-rsa" ;
88import * as client from "openid-client" ;
9+ import { skipSubjectCheck , WWWAuthenticateChallengeError } from "openid-client" ;
910
1011export interface AuthPluginOptions {
1112 /** The discovery URL of the OpenID Connect provider. */
@@ -52,6 +53,15 @@ export const AuthResponseSchema: ResponseSchema = {
5253 ) ,
5354} ;
5455
56+ class UnauthorizedError extends Error {
57+ cause : Error ;
58+ constructor ( cause : Error ) {
59+ super ( ) ;
60+ this . cause = cause ;
61+ this . name = "UnauthorizedError" ;
62+ }
63+ }
64+
5565/**
5666 * The Auth plugin adds authentication ability to the Fastify instance.
5767 *
@@ -66,21 +76,28 @@ export default fp<AuthPluginOptions>(async (fastify, opts) => {
6676 fastify . log . warn ( "Skip Auth: ON" ) ;
6777 }
6878
69- const key = await ( async ( ) => {
79+ const config = await ( async ( ) => {
7080 if ( skip ) {
7181 return null ;
7282 } else {
73- const config = await client . discovery (
83+ return await client . discovery (
7484 new URL ( opts . authDiscoveryURL ) ,
7585 opts . authClientID ,
7686 ) ;
87+ }
88+ } ) ( ) ;
89+
90+ const key = await ( async ( ) => {
91+ if ( skip ) {
92+ return null ;
93+ } else {
7794 fastify . log . info (
78- { opts, metadata : config ? .serverMetadata ( ) } ,
95+ { opts, metadata : config ! . serverMetadata ( ) } ,
7996 "Successfully discovered the OpenID Connect provider." ,
8097 ) ;
8198
8299 const jwksClient = new JwksClient ( {
83- jwksUri : config . serverMetadata ( ) . jwks_uri ?? "" ,
100+ jwksUri : config ! . serverMetadata ( ) . jwks_uri ?? "" ,
84101 } ) ;
85102 return async ( header : JwtHeader , callback : SigningKeyCallback ) => {
86103 jwksClient . getSigningKey ( header . kid , ( err , key ) => {
@@ -91,9 +108,47 @@ export default fp<AuthPluginOptions>(async (fastify, opts) => {
91108 }
92109 } ) ( ) ;
93110
111+ async function verify ( token : string ) {
112+ try {
113+ const info = await new Promise < jwt . JwtPayload > ( ( resolve , reject ) => {
114+ jwt . verify ( token , key ! , ( err , info ) => {
115+ if ( err ) {
116+ return reject ( err ) ;
117+ }
118+ resolve ( info as jwt . JwtPayload ) ;
119+ } ) ;
120+ } ) ;
121+ return info . email && getUsernameFromEmail ( info . email ) ;
122+ } catch ( e ) {
123+ if (
124+ e instanceof jwt . JsonWebTokenError ||
125+ e instanceof jwt . TokenExpiredError ||
126+ e instanceof jwt . NotBeforeError
127+ ) {
128+ throw new UnauthorizedError ( e ) ;
129+ }
130+ throw e ;
131+ }
132+ }
133+
134+ async function verifyLegacy ( token : string ) {
135+ try {
136+ const info = await client . fetchUserInfo ( config ! , token , skipSubjectCheck ) ;
137+ return info . email && getUsernameFromEmail ( info . email ) ;
138+ } catch ( e ) {
139+ if (
140+ e instanceof WWWAuthenticateChallengeError &&
141+ e . response ?. status === 401
142+ ) {
143+ throw new UnauthorizedError ( new Error ( await e . response . text ( ) ) ) ;
144+ }
145+ throw e ;
146+ }
147+ }
148+
94149 fastify . decorateRequest ( "user" , undefined ) ;
95150 fastify . decorate (
96- "auth " ,
151+ "authPlugin " ,
97152 async function ( request : FastifyRequest , reply : FastifyReply ) {
98153 if ( skip ) return ;
99154 if ( ! key ) return ;
@@ -115,23 +170,24 @@ export default fp<AuthPluginOptions>(async (fastify, opts) => {
115170 }
116171
117172 try {
118- const info = await new Promise < jwt . JwtPayload > ( ( resolve , reject ) => {
119- jwt . verify ( token , key , ( err , info ) => {
120- if ( err ) {
121- return reject ( err ) ;
122- }
123- resolve ( info as jwt . JwtPayload ) ;
124- } ) ;
173+ // Verify the token and set the user in the request.
174+ // If the token cannot be verified by the modern method,
175+ // fall back to the legacy method.
176+ request . user = await verify ( token ) . catch ( async ( e ) => {
177+ if ( e instanceof UnauthorizedError ) {
178+ fastify . log . debug (
179+ "Modern verification failed, falling back to legacy method." ,
180+ ) ;
181+ return await verifyLegacy ( token ) . catch ( ( ) => {
182+ throw e ;
183+ } ) ;
184+ }
185+ throw e ;
125186 } ) ;
126- request . user = info . email && getUsernameFromEmail ( info . email ) ;
127187 } catch ( e ) {
128- console . log ( e ) ;
129- if (
130- e instanceof jwt . JsonWebTokenError ||
131- e instanceof jwt . TokenExpiredError ||
132- e instanceof jwt . NotBeforeError
133- ) {
134- return reply . status ( 401 ) . send ( `${ e . name } : ${ e . message } ` ) ;
188+ if ( e instanceof UnauthorizedError ) {
189+ const cause = e . cause ;
190+ return reply . status ( 401 ) . send ( `${ cause . name } : ${ cause . message } ` ) ;
135191 }
136192 throw e ;
137193 }
@@ -140,12 +196,13 @@ export default fp<AuthPluginOptions>(async (fastify, opts) => {
140196} ) ;
141197
142198function getUsernameFromEmail ( email : string ) : string {
143- return email . split ( "@" ) [ 0 ] ;
199+ const [ username ] = email . split ( "@" ) ;
200+ return username ;
144201}
145202
146203declare module "fastify" {
147204 export interface FastifyInstance {
148- auth ( request : FastifyRequest , reply : FastifyReply ) : Promise < void > ;
205+ authPlugin ( request : FastifyRequest , reply : FastifyReply ) : Promise < void > ;
149206 }
150207
151208 export interface FastifyRequest {
0 commit comments