Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-amplify|adapter-nextjs): add runtimeOptions.cookies to createServerRunner #13788

Open
wants to merge 1 commit into
base: feat/server-auth/main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading