import fastify, { FastifyInstance } from 'fastify' import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd' import fastifyOauth2, { FastifyOAuth2Options, Credentials, OAuth2Namespace, OAuth2Token, ProviderConfiguration, UserInfoExtraOptions } from '..' import type { ModuleOptions } from 'simple-oauth2' /** * Preparing some data for testing. */ const auth = fastifyOauth2.GOOGLE_CONFIGURATION const scope = ['r_emailaddress', 'r_basicprofile'] const tags = ['oauth2', 'oauth'] const credentials: Credentials = { client: { id: 'test_id', secret: 'test_secret', }, auth, } const simpleOauth2Options: ModuleOptions = { client: { id: 'test_id', secret: 'test_secret', }, auth, } const OAuth2NoneOptional: FastifyOAuth2Options = { name: 'testOAuthName', credentials, callbackUri: 'http://localhost/testOauth/callback' } const OAuth2Options: FastifyOAuth2Options = { name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, generateStateFunction: function () { expectType(this) return 'test' }, checkStateFunction: function () { expectType(this) return true }, startRedirectPath: '/login/testOauth', cookie: { secure: true, sameSite: 'none' }, redirectStateCookieName: 'redirect-state-cookie', verifierCookieName: 'verifier-cookie', } expectAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, startRedirectPath: '/login/testOauth', pkce: 'S256' }) expectAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: req => `${req.protocol}://${req.hostname}/callback`, callbackUriParams: {}, startRedirectPath: '/login/testOauth', pkce: 'S256' }) expectAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, startRedirectPath: '/login/testOauth', discovery: { issuer: 'https://idp.mycompany.com' } }) expectNotAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, startRedirectPath: '/login/testOauth', discovery: { issuer: 1 } }) expectAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, startRedirectPath: '/login/testOauth', pkce: 'plain' }) expectNotAssignable({ name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', callbackUriParams: {}, generateStateFunction: () => { }, checkStateFunction: () => { }, startRedirectPath: '/login/testOauth', pkce: 'SOMETHING' }) const server = fastify() server.register(fastifyOauth2, OAuth2NoneOptional) server.register(fastifyOauth2, OAuth2Options) server.register(fastifyOauth2, { name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', checkStateFunction: () => true, }) expectError(server.register(fastifyOauth2, { name: 'testOAuthName', scope, credentials, callbackUri: 'http://localhost/testOauth/callback', checkStateFunction: () => true, startRedirectPath: 2, })) declare module 'fastify' { // Developers need to define this in their code like they have to do with all decorators. interface FastifyInstance { testOAuthName: OAuth2Namespace; } } /** * Actual testing. */ expectType(auth) expectType(scope) expectType(tags) expectType(credentials) // Ensure duplicayed simple-oauth2 are compatible with simple-oauth2 expectAssignable>({ auth: { tokenHost: '' }, ...credentials }) expectAssignable(auth) // Ensure published types of simple-oauth2 are accepted expectAssignable(simpleOauth2Options) expectAssignable(simpleOauth2Options.auth) expectError(fastifyOauth2()) // error because missing required arguments expectError(fastifyOauth2(server, {}, () => { })) // error because missing required options expectAssignable(fastifyOauth2.DISCORD_CONFIGURATION) expectAssignable(fastifyOauth2.FACEBOOK_CONFIGURATION) expectAssignable(fastifyOauth2.GITHUB_CONFIGURATION) expectAssignable(fastifyOauth2.GITLAB_CONFIGURATION) expectAssignable(fastifyOauth2.GOOGLE_CONFIGURATION) expectAssignable(fastifyOauth2.LINKEDIN_CONFIGURATION) expectAssignable(fastifyOauth2.MICROSOFT_CONFIGURATION) expectAssignable(fastifyOauth2.SPOTIFY_CONFIGURATION) expectAssignable(fastifyOauth2.VKONTAKTE_CONFIGURATION) expectAssignable(fastifyOauth2.TWITCH_CONFIGURATION) expectAssignable(fastifyOauth2.VATSIM_CONFIGURATION) expectAssignable(fastifyOauth2.VATSIM_DEV_CONFIGURATION) expectAssignable(fastifyOauth2.EPIC_GAMES_CONFIGURATION) expectAssignable(fastifyOauth2.YANDEX_CONFIGURATION) server.get('/testOauth/callback', async (request, reply) => { expectType(server.testOAuthName) expectType(server.oauth2TestOAuthName) expectType(await server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request)) expectType>(server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request)) expectType(await server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request, reply)) expectType>(server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request, reply)) expectType( server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request, (_err: any, _t: OAuth2Token): void => { }) ) expectType( server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request, reply, (_err: any, _t: OAuth2Token): void => { }) ) // error because Promise should not return void expectError(await server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request)) // error because non-Promise function call should return void and have a callback argument expectError( server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request, (_err: any, _t: OAuth2Token): void => { }) ) // error because function call does not pass a callback as second argument. expectError(server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request)) const token = await server.testOAuthName.getAccessTokenFromAuthorizationCodeFlow(request) if (token.token.refresh_token) { expectType( await server.testOAuthName.getNewAccessTokenUsingRefreshToken(token.token, {}) ) expectType>( server.testOAuthName.getNewAccessTokenUsingRefreshToken(token.token, {}) ) expectType( server.testOAuthName.getNewAccessTokenUsingRefreshToken( token.token, {}, (_err: any, _t: OAuth2Token): void => { } ) ) // Expect error because Promise should not return void expectError(server.testOAuthName.revokeToken(token.token, 'access_token', undefined)) // Correct way expectType>(server.testOAuthName.revokeToken(token.token, 'access_token', undefined)) // Expect error because invalid Type test isn't an access_token or refresh_token expectError>(server.testOAuthName.revokeToken(token.token, 'test', undefined)) // Correct way expectType( server.testOAuthName.revokeToken(token.token, 'refresh_token', undefined, (_err: any): void => { }) ) // Expect error because invalid Type test isn't an access_token or refresh_token expectError( server.testOAuthName.revokeToken(token.token, 'test', undefined, (_err: any): void => { }) ) // Expect error because invalid Type test isn't an access_token or refresh_token expectError( server.testOAuthName.revokeToken(token.token, 'access_token', undefined, undefined) ) // Expect error because Promise should not return void expectError(server.testOAuthName.revokeAllToken(token.token, undefined)) // Correct way expectType>(server.testOAuthName.revokeAllToken(token.token, undefined)) // Correct way too expectType(server.testOAuthName.revokeAllToken(token.token, undefined, (_err: any): void => { })) // Invalid content expectError(server.testOAuthName.revokeAllToken(token.token, undefined, undefined)) // error because Promise should not return void expectError(await server.testOAuthName.getNewAccessTokenUsingRefreshToken(token.token, {})) // error because non-Promise function call should return void and have a callback argument expectError( server.testOAuthName.getNewAccessTokenUsingRefreshToken( token.token, {}, (_err: any, _t: OAuth2Token): void => { } ) ) // error because function call does not pass a callback as second argument. expectError(server.testOAuthName.getNewAccessTokenUsingRefreshToken(token.token, {})) } expectType>(server.testOAuthName.generateAuthorizationUri(request, reply)) expectType(server.testOAuthName.generateAuthorizationUri(request, reply, (_err) => {})) // BEGIN userinfo tests expectType>(server.testOAuthName.userinfo(token.token)) expectType>(server.testOAuthName.userinfo(token.token.access_token)) expectType(await server.testOAuthName.userinfo(token.token.access_token)) expectType(server.testOAuthName.userinfo(token.token.access_token, () => {})) expectType(server.testOAuthName.userinfo(token.token.access_token, undefined, () => {})) expectAssignable({ method: 'GET', params: {}, via: 'header' }) expectAssignable({ method: 'POST', params: { a: 1 }, via: 'header' }) expectAssignable({ via: 'body' }) expectNotAssignable({ via: 'donkey' }) expectNotAssignable({ something: 1 }) // END userinfo tests expectType(await server.testOAuthName.generateAuthorizationUri(request, reply)) // error because missing reply argument expectError(server.testOAuthName.generateAuthorizationUri(request)) return { access_token: token.token.access_token, } })