Skip to content

Commit 189955b

Browse files
committed
Add refresh-token endpoint
1 parent e55a069 commit 189955b

File tree

4 files changed

+106
-14
lines changed

4 files changed

+106
-14
lines changed

api/src/auth0.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ export const loginWithPasswordlessCode = withRetries('loginPWL', async (email: s
8989
})).data
9090
);
9191

92+
export const refreshToken = withRetries('refreshToken', async (refreshToken: string) =>
93+
(await authClient.oauth.refreshTokenGrant({
94+
grant_type: 'refresh_token',
95+
refresh_token: refreshToken
96+
})).data
97+
)
98+
9299
export type User = auth0.GetUsers200ResponseOneOfInner & {
93100
app_metadata: AppMetadata
94101
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { initSentry, catchErrors, StatusError } from '../../errors';
2+
initSentry();
3+
4+
import { getCorsResponseHeaders } from '../../cors';
5+
import * as auth0 from '../../auth0';
6+
7+
export const handler = catchErrors(async (event) => {
8+
let headers = getCorsResponseHeaders(event);
9+
10+
if (event.httpMethod === 'OPTIONS') {
11+
return { statusCode: 200, headers, body: '' };
12+
} else if (event.httpMethod !== 'POST') {
13+
return { statusCode: 405, headers, body: '' };
14+
}
15+
16+
let refreshToken;
17+
try {
18+
({ refreshToken } = JSON.parse(event.body!));
19+
} catch (e) {
20+
throw new StatusError(400, 'Invalid request body');
21+
}
22+
23+
if (!refreshToken) throw new StatusError(400, 'Refresh token is required');
24+
25+
const result = await auth0.refreshToken(refreshToken);
26+
27+
return {
28+
statusCode: 200,
29+
headers: { ...headers,
30+
'content-type': 'application/json'
31+
},
32+
body: JSON.stringify({
33+
accessToken: result.access_token,
34+
expiresAt: Date.now() + result.expires_in * 1000
35+
})
36+
};
37+
});

api/src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ apiRouter.get('/redirect-paypro-to-thank-you', lambdaWrapper('redirect-paypro-to
9595

9696
apiRouter.post('/auth/send-code', lambdaWrapper('auth/send-code'));
9797
apiRouter.post('/auth/login', lambdaWrapper('auth/login'));
98+
apiRouter.post('/auth/refresh-token', lambdaWrapper('auth/refresh-token'));
9899

99100
apiRouter.post('/update-team', lambdaWrapper('update-team'));
100101
apiRouter.post('/update-team-size', lambdaWrapper('update-team-size'));

api/test/auth.spec.ts

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import { expect } from 'chai';
44
import { DestroyableServer } from "destroyable-server";
55
import { AUTH0_PORT, auth0Server, startServer } from "./test-util";
66

7+
const TOKEN_RESPONSE = {
8+
"access_token": "at",
9+
"refresh_token": "rt",
10+
"scope": "openid email offline_access",
11+
"expires_in": 86400,
12+
"token_type": "Bearer"
13+
};
14+
715
describe("API auth endpoints", () => {
816

917
let apiServer: DestroyableServer;
@@ -73,13 +81,7 @@ describe("API auth endpoints", () => {
7381
describe("/auth/login", () => {
7482

7583
it("returns a 400 if you don't provide a body", async () => {
76-
const tokenEndpoint = await auth0Server.forPost('/oauth/token').thenJson(200, {
77-
"access_token": "at",
78-
"refresh_token": "rt",
79-
"scope": "openid email offline_access",
80-
"expires_in": 86400,
81-
"token_type": "Bearer"
82-
});
84+
const tokenEndpoint = await auth0Server.forPost('/oauth/token').thenJson(200, TOKEN_RESPONSE);
8385

8486
const response = await fetch(`${apiAddress}/api/auth/login`, {
8587
method: 'POST'
@@ -114,13 +116,7 @@ describe("API auth endpoints", () => {
114116
scope: 'openid email offline_access app_metadata',
115117
grant_type: 'http://auth0.com/oauth/grant-type/passwordless/otp'
116118
})
117-
.thenJson(200, {
118-
"access_token": "at",
119-
"refresh_token": "rt",
120-
"scope": "openid email offline_access",
121-
"expires_in": 86400,
122-
"token_type": "Bearer"
123-
});
119+
.thenJson(200, TOKEN_RESPONSE);
124120

125121
const response = await fetch(`${apiAddress}/api/auth/login`, {
126122
method: 'POST',
@@ -140,4 +136,55 @@ describe("API auth endpoints", () => {
140136

141137
});
142138

139+
describe("/auth/refresh-token", () => {
140+
141+
it("returns a 400 if you don't provide a body", async () => {
142+
const tokenEndpoint = await auth0Server.forPost('/oauth/token').thenJson(200, TOKEN_RESPONSE);
143+
144+
const response = await fetch(`${apiAddress}/api/auth/refresh-token`, {
145+
method: 'POST'
146+
});
147+
148+
expect(response.status).to.equal(400);
149+
expect(await tokenEndpoint.getSeenRequests()).to.have.length(0);
150+
});
151+
152+
it("returns a 400 if you don't provide a refreshToken", async () => {
153+
const tokenEndpoint = await auth0Server.forPost('/oauth/token').thenReply(200);
154+
155+
const response = await fetch(`${apiAddress}/api/auth/refresh-token`, {
156+
method: 'POST',
157+
headers: { 'content-type': 'application/json' },
158+
body: JSON.stringify({ })
159+
});
160+
161+
expect(response.status).to.equal(400);
162+
expect(await tokenEndpoint.getSeenRequests()).to.have.length(0);
163+
});
164+
165+
it("sends a request to Auth0 to refresh the token", async () => {
166+
const refreshToken = 'rt';
167+
const tokenEndpoint = await auth0Server.forPost('/oauth/token')
168+
.withForm({
169+
refresh_token: refreshToken,
170+
grant_type: 'refresh_token'
171+
})
172+
.thenJson(200, TOKEN_RESPONSE);
173+
174+
const response = await fetch(`${apiAddress}/api/auth/refresh-token`, {
175+
method: 'POST',
176+
headers: { 'content-type': 'application/json' },
177+
body: JSON.stringify({ refreshToken })
178+
});
179+
180+
expect(response.status).to.equal(200);
181+
expect(await tokenEndpoint.getSeenRequests()).to.have.length(1);
182+
183+
const result = await response.json();
184+
expect(result.accessToken).to.equal('at');
185+
expect(result.expiresAt).to.be.greaterThan(Date.now());
186+
expect(result.expiresAt).to.be.lessThan(Date.now() + 100_000_000);
187+
});
188+
});
189+
143190
});

0 commit comments

Comments
 (0)