Skip to content

Commit

Permalink
feat(next): grant access token and check expiration
Browse files Browse the repository at this point in the history
  • Loading branch information
wangsijie committed Jul 20, 2022
1 parent 56ed51e commit d192f64
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 2 deletions.
20 changes: 19 additions & 1 deletion packages/next/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const getIdTokenClaims = jest.fn(() => ({
sub: 'user_id',
}));
const signOut = jest.fn();
const getAccessToken = jest.fn(async () => true);

jest.mock('./storage', () =>
jest.fn(() => ({
Expand All @@ -47,7 +48,7 @@ jest.mock('@logto/node', () =>
signIn();
},
handleSignInCallback,
isAuthenticated: true,
getAccessToken,
getIdTokenClaims,
signOut: () => {
navigate(configs.baseUrl);
Expand Down Expand Up @@ -100,6 +101,22 @@ describe('Next', () => {
});

describe('withLogtoApiRoute', () => {
it('should set isAuthenticated to false when unable to getAccessToken', async () => {
getAccessToken.mockRejectedValueOnce(new Error('Unauthorized'));
const client = new LogtoClient(configs);
await testApiHandler({
handler: client.withLogtoApiRoute((request, response) => {
expect(request.user).toBeDefined();
response.json(request.user);
}),
test: async ({ fetch }) => {
const response = await fetch({ method: 'GET', redirect: 'manual' });
await expect(response.json()).resolves.toEqual({ isAuthenticated: false });
},
});
expect(getAccessToken).toHaveBeenCalled();
});

it('should assign `user` to `request`', async () => {
const client = new LogtoClient(configs);
await testApiHandler({
Expand All @@ -111,6 +128,7 @@ describe('Next', () => {
await fetch({ method: 'GET', redirect: 'manual' });
},
});
expect(getAccessToken).toHaveBeenCalled();
expect(getIdTokenClaims).toHaveBeenCalled();
});
});
Expand Down
15 changes: 14 additions & 1 deletion packages/next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { LogtoNextConfig, LogtoUser } from './types';

export type { LogtoUser } from './types';

// Refresh token can be revoked, so it is authenticated only when we have a unexpired access token
const checkIsAuthenticatedByAccessToken = async (client: NodeClient): Promise<boolean> => {
try {
await client.getAccessToken();

return true;
} catch {
return false;
}
};

export default class LogtoClient {
private navigateUrl?: string;
private storage?: NextStorage;
Expand Down Expand Up @@ -55,7 +66,8 @@ export default class LogtoClient {
withLogtoApiRoute = (handler: NextApiHandler): NextApiHandler =>
this.withIronSession(async (request, response) => {
const nodeClient = this.createNodeClient(request);
const { isAuthenticated } = nodeClient;
const isAuthenticated = await checkIsAuthenticatedByAccessToken(nodeClient);
await this.storage?.save();

const user: LogtoUser = {
isAuthenticated,
Expand Down Expand Up @@ -91,6 +103,7 @@ export default class LogtoClient {
password: this.config.cookieSecret,
cookieOptions: {
secure: this.config.cookieSecure,
maxAge: 14 * 24 * 60 * 60,
},
});
}
Expand Down
8 changes: 8 additions & 0 deletions packages/next/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { Storage, StorageKey } from '@logto/node';
import { NextRequestWithIronSession } from './types';

export default class NextStorage implements Storage {
private sessionChanged = false;
constructor(private readonly request: NextRequestWithIronSession) {}

async setItem(key: StorageKey, value: string) {
this.request.session[key] = value;
this.sessionChanged = true;
}

getItem(key: StorageKey) {
Expand All @@ -21,9 +23,15 @@ export default class NextStorage implements Storage {

removeItem(key: StorageKey) {
this.request.session[key] = undefined;
this.sessionChanged = true;
}

async save() {
if (!this.sessionChanged) {
return;
}

await this.request.session.save();
this.sessionChanged = false;
}
}

0 comments on commit d192f64

Please sign in to comment.