import { EzApp , EzBackendInstance , EzBackendOpts } from '@ezbackend/common' ; import type { FastifyInstance , FastifyReply , FastifyRequest , RouteOptions , } from 'fastify' ; import fastifyPassport from 'fastify-passport' ; import { DeserializeFunction , SerializeFunction , } from 'fastify-passport/dist/Authenticator' ; import { AnyStrategy } from 'fastify-passport/dist/strategies' ; import { defaultConfig , EzBackendAuthOpts } from '../auth' ; declare module 'fastify' { interface PassportUser { [ index ] : any ; } } export interface ProviderOptions { deprecated successRedirectURL ? : string ; deprecated failureRedirectURL ? : string ; } type ProviderName = keyof EzBackendAuthOpts ; export abstract class BaseProvider extends EzApp { providerName : ProviderName ; modelName : string ; constructor ( providerName , modelName ) { super ( ) ; this . providerName = providerName ; this . modelName = modelName ; this . setDefaultOpts ( defaultConfig ) ; this . setHandler ( `Add ${ this . providerName } Auth Provider` , async ( instance , opts ) => { this . addProvider ( instance , opts ) ; } ) ; } abstract addStrategy ( instance , server , opts ) ; abstract getLoginRoute ( server , opts ) ; abstract getLogoutRoute ( server , opts ) ; getCallbackRoute ? ( server , opts ) ; addProvider ( instance , fullOpts ) { const opts = this . getOpts ( 'auth' , fullOpts ) ; const providerOpts = { ... ( opts [ this . providerName ] as ProviderOptions ) , ... opts , } ; instance . server . register ( async ( server , opts ) => { const [ name , Strategy ] = this . addStrategy ( instance , server , providerOpts ) ; fastifyPassport . use ( name , Strategy ) ; fastifyPassport . registerUserSerializer ( this . registerUserSerializer ( instance , providerOpts ) ) ; fastifyPassport . registerUserDeserializer ( this . registerUserDeserializer ( instance , providerOpts ) ) ; server . route ( this . getLoginRoute ( server as any , providerOpts ) as any ) ; server . route ( this . getLogoutRoute ( server as any , providerOpts ) as any ) ; if ( this . getCallbackRoute ) { server . route ( this . getCallbackRoute ( server as any , providerOpts ) as any ) ; } } ) ; } getRoutePrefixNoPrePostSlash ( server ) { const fullRoute = `auth/${ this . providerName }` ; return fullRoute ; } getFullRoutePrefixNoPrePostSlash ( server ) { const encapsulatedPrefix = server . prefix . replace ( /^\// , '' ) ; if ( encapsulatedPrefix === '' ) { return this . getRoutePrefixNoPrePostSlash ( server ) ; } else { return `${ encapsulatedPrefix }/${ this . getRoutePrefixNoPrePostSlash ( server ) }` ; } } getCallbackURL ( server ) { const urlPath = `${ this . getFullRoutePrefixNoPrePostSlash ( server ) }/callback` ; let result ; if ( process . env . NODE_ENV === 'production' && process . env . PRODUCTION_URL ) { const callbackURL = new URL ( urlPath , process . env . PRODUCTION_URL ) . href ; result = callbackURL ; } else { result = `/${ urlPath }` ; } return result ; } defaultCallbackHandler ( instance , id , profile , cb ) { const repo = instance . orm . getRepository ( this . modelName ) ; const model = { [ `${ this . providerName }Id` ] : id , [ `${ this . providerName }Data` ] : profile , } ; const serializedID = `${ this . providerName }-${ id }` ; repo . save ( model ) . then ( ( ) => { cb ( undefined , serializedID ) ; } , ( e ) => { if ( String ( e . driverError ) . toLowerCase ( ) . includes ( 'unique' ) ) { cb ( undefined , serializedID ) ; } else { cb ( e ) ; } } ) ; } defaultLogoutHandler ( req , res , opts ) { req . logOut ( ) . then ( ( ) => { res . redirect ( opts . successRedirectURL ) ; } ) ; } registerUserSerializer ( instance , opts ) { return async function serializer ( id , req ) { return id ; } ; } registerUserDeserializer ( instance , opts ) { const that = this ; return async function deserializer ( providerAndId , req ) { if ( providerAndId . startsWith ( `${ that . providerName }-` ) ) { const id = providerAndId . replace ( `${ that . providerName }-` , '' ) ; const userRepo = instance . orm . getRepository ( that . modelName ) ; const fullUser = await userRepo . findOne ( { [ `${ that . providerName }Id` ] : id , } ) ; if ( fullUser ) { return fullUser ; } else { return null ; } } else { throw 'pass' ; } } ; } }