-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adapter-nextjs): add createAuthRouteHandlers to createServerRunner
- Loading branch information
Showing
18 changed files
with
887 additions
and
2 deletions.
There are no files selected for viewing
217 changes: 217 additions & 0 deletions
217
packages/adapter-nextjs/__tests__/auth/createAuthRouteHandlersFactory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import { ResourcesConfig } from 'aws-amplify'; | ||
import { | ||
assertOAuthConfig, | ||
assertTokenProviderConfig, | ||
} from '@aws-amplify/core/internals/utils'; | ||
|
||
import { createAuthRouteHandlersFactory } from '../../src/auth/createAuthRouteHandlersFactory'; | ||
import { handleAuthApiRouteRequestForAppRouter } from '../../src/auth/handleAuthApiRouteRequestForAppRouter'; | ||
import { handleAuthApiRouteRequestForPagesRouter } from '../../src/auth/handleAuthApiRouteRequestForPagesRouter'; | ||
import { NextServer } from '../../src'; | ||
import { | ||
AuthRouteHandlers, | ||
CreateAuthRoutesHandlersInput, | ||
} from '../../src/auth/types'; | ||
import { | ||
isAuthRoutesHandlersContext, | ||
isNextApiRequest, | ||
isNextApiResponse, | ||
isNextRequest, | ||
} from '../../src/auth/utils'; | ||
|
||
jest.mock('@aws-amplify/core/internals/utils'); | ||
jest.mock('../../src/auth/handleAuthApiRouteRequestForAppRouter'); | ||
jest.mock('../../src/auth/handleAuthApiRouteRequestForPagesRouter'); | ||
jest.mock('../../src/auth/utils'); | ||
|
||
const mockAmplifyConfig: ResourcesConfig = { | ||
Auth: { | ||
Cognito: { | ||
identityPoolId: '123', | ||
userPoolId: 'abc', | ||
userPoolClientId: 'def', | ||
loginWith: { | ||
oauth: { | ||
domain: 'example.com', | ||
responseType: 'code', | ||
redirectSignIn: ['https://example.com/signin'], | ||
redirectSignOut: ['https://example.com/signout'], | ||
scopes: ['openid', 'email'], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
const mockRuntimeOptions: NextServer.CreateServerRunnerRuntimeOptions = { | ||
cookies: { | ||
sameSite: 'strict', | ||
}, | ||
}; | ||
const mockAssertTokenProviderConfig = jest.mocked(assertTokenProviderConfig); | ||
const mockAssertOAuthConfig = jest.mocked(assertOAuthConfig); | ||
const mockHandleAuthApiRouteRequestForAppRouter = jest.mocked( | ||
handleAuthApiRouteRequestForAppRouter, | ||
); | ||
const mockHandleAuthApiRouteRequestForPagesRouter = jest.mocked( | ||
handleAuthApiRouteRequestForPagesRouter, | ||
); | ||
const mockIsNextApiRequest = jest.mocked(isNextApiRequest); | ||
const mockIsNextApiResponse = jest.mocked(isNextApiResponse); | ||
const mockIsNextRequest = jest.mocked(isNextRequest); | ||
const mockIsAuthRoutesHandlersContext = jest.mocked( | ||
isAuthRoutesHandlersContext, | ||
); | ||
|
||
describe('createAuthRoutesHandlersFactory', () => { | ||
const existingProcessEnvVars = { ...process.env }; | ||
const modifiedProcessEnvVars = { | ||
...process.env, | ||
AMPLIFY_APP_ORIGIN: 'https://example.com', | ||
}; | ||
|
||
beforeEach(() => { | ||
process.env = modifiedProcessEnvVars; | ||
}); | ||
|
||
afterEach(() => { | ||
process.env = existingProcessEnvVars; | ||
}); | ||
|
||
it('throws an error if the AMPLIFY_APP_ORIGIN environment variable is not set', () => { | ||
process.env = { ...existingProcessEnvVars, AMPLIFY_APP_ORIGIN: undefined }; | ||
expect(() => | ||
createAuthRouteHandlersFactory({ | ||
config: mockAmplifyConfig, | ||
runtimeOptions: mockRuntimeOptions, | ||
}), | ||
).toThrow('Could not find the AMPLIFY_APP_ORIGIN environment variable.'); | ||
}); | ||
|
||
it('calls config assertion functions to validate the Auth configuration', () => { | ||
createAuthRouteHandlersFactory({ | ||
config: mockAmplifyConfig, | ||
runtimeOptions: mockRuntimeOptions, | ||
}); | ||
|
||
expect(mockAssertTokenProviderConfig).toHaveBeenCalledWith( | ||
mockAmplifyConfig.Auth?.Cognito, | ||
); | ||
expect(mockAssertOAuthConfig).toHaveBeenCalledWith( | ||
mockAmplifyConfig.Auth!.Cognito, | ||
); | ||
}); | ||
|
||
describe('the created route handler function', () => { | ||
const testCreateAuthRoutesHandlersFactoryInput = { | ||
config: mockAmplifyConfig, | ||
runtimeOptions: mockRuntimeOptions, | ||
}; | ||
const testCreateAuthRoutesHandlersInput: CreateAuthRoutesHandlersInput = { | ||
customState: 'random-state', | ||
redirectOnSignInComplete: '/home', | ||
redirectOnSignOutComplete: '/login', | ||
}; | ||
let handler: AuthRouteHandlers; | ||
|
||
beforeAll(() => { | ||
process.env = modifiedProcessEnvVars; | ||
const createAuthRoutesHandlers = createAuthRouteHandlersFactory( | ||
testCreateAuthRoutesHandlersFactoryInput, | ||
); | ||
handler = createAuthRoutesHandlers(testCreateAuthRoutesHandlersInput); | ||
}); | ||
|
||
afterAll(() => { | ||
process.env = existingProcessEnvVars; | ||
}); | ||
|
||
afterEach(() => { | ||
mockIsAuthRoutesHandlersContext.mockReset(); | ||
mockIsNextApiRequest.mockReset(); | ||
mockIsNextApiResponse.mockReset(); | ||
mockIsNextRequest.mockReset(); | ||
}); | ||
|
||
it('calls handleAuthApiRouteRequestForPagesRouter when 1st param is a NextApiRequest and 2nd param is a NextApiResponse', async () => { | ||
const param1 = {} as any; | ||
const param2 = {} as any; | ||
mockIsNextApiRequest.mockReturnValueOnce(true); | ||
mockIsNextApiResponse.mockReturnValueOnce(true); | ||
mockIsNextRequest.mockReturnValueOnce(false); | ||
mockIsAuthRoutesHandlersContext.mockReturnValueOnce(false); | ||
|
||
await handler(param1, param2); | ||
|
||
expect(mockHandleAuthApiRouteRequestForPagesRouter).toHaveBeenCalledWith({ | ||
request: param1, | ||
response: param2, | ||
handlerInput: testCreateAuthRoutesHandlersInput, | ||
oAuthConfig: mockAmplifyConfig.Auth!.Cognito!.loginWith!.oauth, | ||
setCookieOptions: mockRuntimeOptions.cookies, | ||
origin: 'https://example.com', | ||
}); | ||
}); | ||
|
||
it('calls handleAuthApiRouteRequestForAppRouter when 1st param is a NextRequest and the 2nd param is a AuthRoutesHandlersContext', async () => { | ||
const request = {} as any; | ||
const context = {} as any; | ||
mockIsNextApiRequest.mockReturnValueOnce(false); | ||
mockIsNextApiResponse.mockReturnValueOnce(false); | ||
mockIsNextRequest.mockReturnValueOnce(true); | ||
mockIsAuthRoutesHandlersContext.mockReturnValueOnce(true); | ||
|
||
await handler(request, context); | ||
|
||
expect(mockHandleAuthApiRouteRequestForAppRouter).toHaveBeenCalledWith({ | ||
request, | ||
handlerContext: context, | ||
handlerInput: testCreateAuthRoutesHandlersInput, | ||
oAuthConfig: mockAmplifyConfig.Auth!.Cognito!.loginWith!.oauth, | ||
setCookieOptions: mockRuntimeOptions.cookies, | ||
origin: 'https://example.com', | ||
}); | ||
}); | ||
|
||
it('throws an error when the request and context/response combination is invalid', () => { | ||
const request = {} as any; | ||
const context = {} as any; | ||
mockIsNextApiRequest.mockReturnValueOnce(false); | ||
mockIsNextApiResponse.mockReturnValueOnce(false); | ||
mockIsNextRequest.mockReturnValueOnce(false); | ||
mockIsAuthRoutesHandlersContext.mockReturnValueOnce(false); | ||
|
||
expect(handler(request, context)).rejects.toThrow( | ||
'Invalid request and context/response combination. The request cannot be handled.', | ||
); | ||
}); | ||
|
||
it('uses default values for parameters that have values as undefined', async () => { | ||
const createAuthRoutesHandlers = createAuthRouteHandlersFactory({ | ||
config: mockAmplifyConfig, | ||
runtimeOptions: undefined, | ||
}); | ||
const handlerWithDefaultParamValues = | ||
createAuthRoutesHandlers(/* undefined */); | ||
|
||
const request = {} as any; | ||
const response = {} as any; | ||
|
||
mockIsNextApiRequest.mockReturnValueOnce(true); | ||
mockIsNextApiResponse.mockReturnValueOnce(true); | ||
mockIsNextRequest.mockReturnValueOnce(false); | ||
mockIsAuthRoutesHandlersContext.mockReturnValueOnce(false); | ||
|
||
await handlerWithDefaultParamValues(request, response); | ||
|
||
expect(handleAuthApiRouteRequestForPagesRouter).toHaveBeenCalledWith({ | ||
request, | ||
response, | ||
handlerInput: {}, | ||
oAuthConfig: mockAmplifyConfig.Auth!.Cognito!.loginWith!.oauth, | ||
setCookieOptions: {}, | ||
origin: 'https://example.com', | ||
}); | ||
}); | ||
}); | ||
}); |
102 changes: 102 additions & 0 deletions
102
packages/adapter-nextjs/__tests__/auth/handleAuthApiRouteRequestForAppRouter.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* @jest-environment node | ||
*/ | ||
import { NextRequest } from 'next/server'; | ||
import { OAuthConfig } from '@aws-amplify/core'; | ||
|
||
import { handleAuthApiRouteRequestForAppRouter } from '../../src/auth/handleAuthApiRouteRequestForAppRouter'; | ||
import { CreateAuthRoutesHandlersInput } from '../../src/auth/types'; | ||
|
||
describe('handleAuthApiRouteRequestForAppRouter', () => { | ||
const testOrigin = 'https://example.com'; | ||
const testHandlerInput: CreateAuthRoutesHandlersInput = { | ||
redirectOnSignInComplete: '/home', | ||
redirectOnSignOutComplete: 'sign-in', | ||
}; | ||
const testHandlerContext = { params: { slug: 'sign-in' } }; | ||
const testOAuthConfig: OAuthConfig = { | ||
domain: 'example.com', | ||
redirectSignIn: ['https://example.com/signin'], | ||
redirectSignOut: ['https://example.com/signout'], | ||
responseType: 'code', | ||
scopes: ['openid', 'email'], | ||
}; | ||
const _ = handleAuthApiRouteRequestForAppRouter; | ||
|
||
it('returns a 405 response when input.request has an unsupported method', () => { | ||
const request = new NextRequest( | ||
new URL('https://example.com/api/auth/sign-in'), | ||
{ | ||
method: 'POST', | ||
}, | ||
); | ||
const response = handleAuthApiRouteRequestForAppRouter({ | ||
request, | ||
handlerContext: testHandlerContext, | ||
handlerInput: testHandlerInput, | ||
oAuthConfig: testOAuthConfig, | ||
setCookieOptions: {}, | ||
origin: testOrigin, | ||
}); | ||
|
||
expect(response.status).toBe(405); | ||
}); | ||
|
||
it('returns a 400 response when handlerContext.params.slug is undefined', () => { | ||
const request = new NextRequest( | ||
new URL('https://example.com/api/auth/sign-in'), | ||
{ | ||
method: 'GET', | ||
}, | ||
); | ||
const response = handleAuthApiRouteRequestForAppRouter({ | ||
request, | ||
handlerContext: { params: { slug: undefined } }, | ||
handlerInput: testHandlerInput, | ||
oAuthConfig: testOAuthConfig, | ||
setCookieOptions: {}, | ||
origin: testOrigin, | ||
}); | ||
|
||
expect(response.status).toBe(400); | ||
}); | ||
|
||
it('returns a 404 response when handlerContext.params.slug is not a supported path', () => { | ||
const request = new NextRequest( | ||
new URL('https://example.com/api/auth/exchange-token'), | ||
{ | ||
method: 'GET', | ||
}, | ||
); | ||
const response = handleAuthApiRouteRequestForAppRouter({ | ||
request, | ||
handlerContext: { params: { slug: 'exchange-token' } }, | ||
handlerInput: testHandlerInput, | ||
oAuthConfig: testOAuthConfig, | ||
setCookieOptions: {}, | ||
origin: testOrigin, | ||
}); | ||
|
||
expect(response.status).toBe(404); | ||
}); | ||
|
||
// TODO(HuiSF): add use cases tests for each supported path when implemented | ||
it('returns a 501 response when handlerContext.params.slug is a supported path', () => { | ||
const request = new NextRequest( | ||
new URL('https://example.com/api/auth/sign-in'), | ||
{ | ||
method: 'GET', | ||
}, | ||
); | ||
const response = handleAuthApiRouteRequestForAppRouter({ | ||
request, | ||
handlerContext: { params: { slug: 'sign-in' } }, | ||
handlerInput: testHandlerInput, | ||
oAuthConfig: testOAuthConfig, | ||
setCookieOptions: {}, | ||
origin: testOrigin, | ||
}); | ||
|
||
expect(response.status).toBe(501); | ||
}); | ||
}); |
Oops, something went wrong.