Skip to content

Commit

Permalink
feat(aws-amplify|adapter-nextjs): add runtimeOptions.cookies to creat…
Browse files Browse the repository at this point in the history
…eServerRunner
  • Loading branch information
HuiSF committed Nov 21, 2024
1 parent ab33a03 commit fa5d86c
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 19 deletions.
94 changes: 77 additions & 17 deletions packages/adapter-nextjs/__tests__/createServerRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,22 @@ const mockAmplifyConfig: ResourcesConfig = {
jest.mock(
'../src/utils/createCookieStorageAdapterFromNextServerContext',
() => ({
createCookieStorageAdapterFromNextServerContext: jest.fn(),
createCookieStorageAdapterFromNextServerContext: jest.fn(() => ({
get: jest.fn(),
set: jest.fn(),
delete: jest.fn(),
getAll: jest.fn(),
})),
}),
);

jest.mock('../src/utils/createTokenValidator', () => ({
createTokenValidator: jest.fn(() => ({
getItem: jest.fn(),
})),
}));
describe('createServerRunner', () => {
let createServerRunner: any;
let createServerRunner: NextServer.CreateServerRunner;

const mockParseAmplifyConfig = jest.fn();
const mockCreateAWSCredentialsAndIdentityIdProvider = jest.fn();
Expand Down Expand Up @@ -124,26 +134,28 @@ describe('createServerRunner', () => {
});

describe('when nextServerContext is not null', () => {
const mockNextServerContext = {
req: {
headers: {
cookie: 'cookie',
},
},
res: {
setHeader: jest.fn(),
},
};
const mockCookieStorageAdapter = {
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
};

it('should create auth providers with cookie storage adapter', () => {
const operation = jest.fn();
const mockCookieStorageAdapter = {
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
};

mockCreateKeyValueStorageFromCookieStorageAdapter.mockReturnValueOnce(
mockCookieStorageAdapter,
);
const mockNextServerContext = {
req: {
headers: {
cookie: 'cookie',
},
},
res: {
setHeader: jest.fn(),
},
};
const { runWithAmplifyServerContext } = createServerRunner({
config: mockAmplifyConfig,
});
Expand All @@ -163,6 +175,54 @@ describe('createServerRunner', () => {
mockCookieStorageAdapter,
);
});

it('should call createKeyValueStorageFromCookieStorageAdapter with specified runtimeOptions.cookies', () => {
const testCookiesOptions: NextServer.CreateServerRunnerRuntimeOptions['cookies'] =
{
domain: '.example.com',
sameSite: 'lax',
expires: new Date('2024-09-05'),
};
mockCreateKeyValueStorageFromCookieStorageAdapter.mockReturnValueOnce(
mockCookieStorageAdapter,
);

const { runWithAmplifyServerContext } = createServerRunner({
config: mockAmplifyConfig,
runtimeOptions: {
cookies: testCookiesOptions,
},
});

runWithAmplifyServerContext({
nextServerContext:
mockNextServerContext as unknown as NextServer.Context,
operation: jest.fn(),
});

expect(
mockCreateKeyValueStorageFromCookieStorageAdapter,
).toHaveBeenCalledWith(
expect.any(Object),
expect.any(Object),
testCookiesOptions,
);

// modify by reference should not affect the original configuration
testCookiesOptions.sameSite = 'strict';
runWithAmplifyServerContext({
nextServerContext:
mockNextServerContext as unknown as NextServer.Context,
operation: jest.fn(),
});

expect(
mockCreateKeyValueStorageFromCookieStorageAdapter,
).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), {
...testCookiesOptions,
sameSite: 'lax',
});
});
});
});
});
Expand Down
2 changes: 2 additions & 0 deletions packages/adapter-nextjs/src/createServerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import { NextServer } from './types';
*/
export const createServerRunner: NextServer.CreateServerRunner = ({
config,
runtimeOptions,
}) => {
const amplifyConfig = parseAmplifyConfig(config);

return {
runWithAmplifyServerContext: createRunWithAmplifyServerContext({
config: amplifyConfig,
runtimeOptions,
}),
};
};
13 changes: 12 additions & 1 deletion packages/adapter-nextjs/src/types/NextServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { GetServerSidePropsContext as NextGetServerSidePropsContext } from 'next
import { NextRequest, NextResponse } from 'next/server.js';
import { cookies } from 'next/headers.js';
import { AmplifyOutputs, LegacyConfig } from 'aws-amplify/adapter-core';
import { AmplifyServer } from '@aws-amplify/core/internals/adapter-core';
import {
AmplifyServer,
CookieStorage,
} from '@aws-amplify/core/internals/adapter-core';
import { ResourcesConfig } from '@aws-amplify/core';

export declare namespace NextServer {
Expand Down Expand Up @@ -73,8 +76,16 @@ export declare namespace NextServer {
input: RunWithContextInput<OperationResult>,
) => Promise<OperationResult>;

export interface CreateServerRunnerRuntimeOptions {
cookies?: Pick<
CookieStorage.SetCookieOptions,
'domain' | 'expires' | 'sameSite'
>;
}

export interface CreateServerRunnerInput {
config: ResourcesConfig | LegacyConfig | AmplifyOutputs;
runtimeOptions?: CreateServerRunnerRuntimeOptions;
}

export interface CreateServerRunnerOutput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import { createCookieStorageAdapterFromNextServerContext } from './createCookieS

export const createRunWithAmplifyServerContext = ({
config: resourcesConfig,
runtimeOptions = { cookies: {} },
}: {
config: ResourcesConfig;
runtimeOptions?: NextServer.CreateServerRunnerRuntimeOptions;
}) => {
const setCookieOptions = {
...runtimeOptions.cookies,
};
const runWithAmplifyServerContext: NextServer.RunOperationWithContext =
async ({ nextServerContext, operation }) => {
// When the Auth config is presented, attempt to create a Amplify server
Expand All @@ -40,6 +45,7 @@ export const createRunWithAmplifyServerContext = ({
userPoolClientId:
resourcesConfig?.Auth.Cognito?.userPoolClientId,
}),
setCookieOptions,
);
const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(
resourcesConfig.Auth,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { createKeyValueStorageFromCookieStorageAdapter } from '../../../src/adapter-core';
import {
CookieStorage,
createKeyValueStorageFromCookieStorageAdapter,
} from '../../../src/adapter-core';
import { defaultSetCookieOptions } from '../../../src/adapter-core/storageFactories/createKeyValueStorageFromCookieStorageAdapter';

const mockCookiesStorageAdapter = {
Expand All @@ -12,6 +15,13 @@ const mockCookiesStorageAdapter = {
};

describe('keyValueStorage', () => {
afterEach(() => {
mockCookiesStorageAdapter.delete.mockClear();
mockCookiesStorageAdapter.get.mockClear();
mockCookiesStorageAdapter.set.mockClear();
mockCookiesStorageAdapter.getAll.mockClear();
});

describe('createKeyValueStorageFromCookiesStorageAdapter', () => {
it('should return a key value storage', () => {
const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter(
Expand Down Expand Up @@ -100,6 +110,31 @@ describe('keyValueStorage', () => {
});
});

describe('passing setCookieOptions parameter', () => {
const testSetCookieOptions: CookieStorage.SetCookieOptions = {
httpOnly: true,
sameSite: 'strict',
expires: new Date('2024-09-05'),
};
const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter(
mockCookiesStorageAdapter,
undefined,
testSetCookieOptions,
);

it('sets item with specified setCookieOptions', async () => {
keyValueStorage.setItem('testKey', 'testValue');
expect(mockCookiesStorageAdapter.set).toHaveBeenCalledWith(
'testKey',
'testValue',
{
...defaultSetCookieOptions,
...testSetCookieOptions,
},
);
});
});

describe('in conjunction with token validator', () => {
const testKey = 'testKey';
const testValue = 'testValue';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ const ONE_YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;
export const createKeyValueStorageFromCookieStorageAdapter = (
cookieStorageAdapter: CookieStorage.Adapter,
validatorMap?: KeyValueStorageMethodValidator,
setCookieOptions: CookieStorage.SetCookieOptions = {},
): KeyValueStorageInterface => {
const overrideCookieAttributes = {
...setCookieOptions,
};

return {
setItem(key, value) {
// Delete the cookie item first then set it. This results:
Expand All @@ -36,6 +41,7 @@ export const createKeyValueStorageFromCookieStorageAdapter = (
cookieStorageAdapter.set(key, value, {
...defaultSetCookieOptions,
expires: new Date(Date.now() + ONE_YEAR_IN_MS),
...overrideCookieAttributes,
});

return Promise.resolve();
Expand Down

0 comments on commit fa5d86c

Please sign in to comment.