Skip to content

Commit dbbfe65

Browse files
authored
Feat(backend, nextjs): introduce redirectToSignIn / redirectToSignup helper (clerk#1108)
1 parent 198144e commit dbbfe65

17 files changed

Lines changed: 532 additions & 27 deletions

File tree

.vscode/launch.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
{
22
"version": "0.2.0",
33
"configurations": [
4+
{
5+
"name": "playground/nextjs: debug server",
6+
"type": "node-terminal",
7+
"request": "launch",
8+
"command": "npm run dev",
9+
"cwd": "${workspaceFolder}/playground/nextjs"
10+
},
411
{
512
"name": "playground/nextjs12: debug server",
613
"type": "node-terminal",

packages/backend/src/exports.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default (QUnit: QUnit) => {
4141
'loadInterstitialFromLocal',
4242
'makeAuthObjectSerializable',
4343
'prunePrivateMetadata',
44+
'redirect',
4445
'sanitizeAuthObject',
4546
'signedInAuthObject',
4647
'signedOutAuthObject',

packages/backend/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './tokens';
88
export * from './tokens/jwt';
99
export * from './tokens/verify';
1010
export { constants } from './constants';
11+
export { redirect } from './redirections';
1112

1213
export type ClerkOptions = CreateBackendApiOptions &
1314
Partial<
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import type QUnit from 'qunit';
2+
import sinon from 'sinon';
3+
4+
import { redirect } from './redirections';
5+
6+
export default (QUnit: QUnit) => {
7+
const { module, test } = QUnit;
8+
9+
module('redirect(redirectAdapter)', () => {
10+
const returnBackUrl = 'http://current.url:3000/path?q=1#hash';
11+
const encodedUrl = 'http%3A%2F%2Fcurrent.url%3A3000%2Fpath%3Fq%3D1%23hash';
12+
13+
test('exposes redirectToSignIn / redirectToSignUp', assert => {
14+
const helpers = redirect({ redirectAdapter: sinon.spy() });
15+
assert.propEqual(Object.keys(helpers).sort(), ['redirectToSignIn', 'redirectToSignUp']);
16+
});
17+
18+
test('raises error with signInUrl as relative path and returnBackUrl missing', assert => {
19+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
20+
const { redirectToSignIn } = redirect({ redirectAdapter: redirectAdapterSpy, signInUrl: '/sign-in' });
21+
22+
assert.raises(
23+
() => redirectToSignIn(),
24+
new Error('destination url or return back url should be an absolute path url!'),
25+
);
26+
});
27+
28+
test('raises error with signInUrl as relative path and returnBackUrl relative path', assert => {
29+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
30+
const { redirectToSignIn } = redirect({ redirectAdapter: redirectAdapterSpy, signInUrl: '/sign-in' });
31+
32+
assert.raises(
33+
() => redirectToSignIn({ returnBackUrl: '/example' }),
34+
new Error('destination url or return back url should be an absolute path url!'),
35+
);
36+
});
37+
38+
test('returns path based url with signInUrl as absolute path and returnBackUrl missing', assert => {
39+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
40+
const { redirectToSignIn } = redirect({
41+
redirectAdapter: redirectAdapterSpy,
42+
signInUrl: 'http://signin.url:3001/sign-in',
43+
});
44+
45+
const result = redirectToSignIn();
46+
assert.equal(result, 'redirectAdapterValue');
47+
assert.ok(redirectAdapterSpy.calledWith(`http://signin.url:3001/sign-in`));
48+
});
49+
50+
test('returns path based url with signInUrl as relative path', assert => {
51+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
52+
const { redirectToSignIn } = redirect({ redirectAdapter: redirectAdapterSpy, signInUrl: '/sign-in' });
53+
54+
const result = redirectToSignIn({ returnBackUrl });
55+
assert.equal(result, 'redirectAdapterValue');
56+
assert.ok(redirectAdapterSpy.calledWith(`http://current.url:3000/sign-in?redirect_url=${encodedUrl}`));
57+
});
58+
59+
test('returns path based url with signInUrl as absolute path', assert => {
60+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
61+
const { redirectToSignIn } = redirect({
62+
redirectAdapter: redirectAdapterSpy,
63+
signInUrl: 'http://signin.url:3001/sign-in',
64+
});
65+
66+
const result = redirectToSignIn({ returnBackUrl });
67+
assert.equal(result, 'redirectAdapterValue');
68+
assert.ok(redirectAdapterSpy.calledWith(`http://signin.url:3001/sign-in?redirect_url=${encodedUrl}`));
69+
});
70+
71+
test('raises error without signInUrl and frontendApi/publishableKey in redirectToSignIn', assert => {
72+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
73+
const { redirectToSignIn } = redirect({ redirectAdapter: redirectAdapterSpy });
74+
75+
assert.raises(
76+
() => redirectToSignIn({ returnBackUrl }),
77+
new Error('signInUrl or frontendApi/publishableKey should be provided!'),
78+
);
79+
});
80+
81+
test('returns path based url with development frontendApi but without signInUrl to redirectToSignIn', assert => {
82+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
83+
const { redirectToSignIn } = redirect({
84+
redirectAdapter: redirectAdapterSpy,
85+
frontendApi: 'clerk.included.katydid-92.lcl.dev',
86+
});
87+
88+
const result = redirectToSignIn({ returnBackUrl });
89+
assert.equal(result, 'redirectAdapterValue');
90+
assert.ok(
91+
redirectAdapterSpy.calledWith(
92+
`https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=${encodedUrl}`,
93+
),
94+
);
95+
});
96+
97+
test('returns path based url with production frontendApi but without signInUrl to redirectToSignIn', assert => {
98+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
99+
const { redirectToSignIn } = redirect({ redirectAdapter: redirectAdapterSpy, frontendApi: 'clerk.example.com' });
100+
101+
const result = redirectToSignIn({ returnBackUrl });
102+
assert.equal(result, 'redirectAdapterValue');
103+
assert.ok(redirectAdapterSpy.calledWith(`https://accounts.example.com/sign-in?redirect_url=${encodedUrl}`));
104+
});
105+
106+
test('returns path based url with development (kima) frontendApi but without signInUrl to redirectToSignIn', assert => {
107+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
108+
const { redirectToSignIn } = redirect({
109+
redirectAdapter: redirectAdapterSpy,
110+
frontendApi: 'included.katydid-92.clerk.accounts.dev',
111+
});
112+
113+
const result = redirectToSignIn({ returnBackUrl });
114+
assert.equal(result, 'redirectAdapterValue');
115+
assert.ok(
116+
redirectAdapterSpy.calledWith(`https://included.katydid-92.accounts.dev/sign-in?redirect_url=${encodedUrl}`),
117+
);
118+
});
119+
120+
test('returns path based url with development publishableKey but without signInUrl to redirectToSignIn', assert => {
121+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
122+
const { redirectToSignIn } = redirect({
123+
redirectAdapter: redirectAdapterSpy,
124+
publishableKey: 'pk_test_Y2xlcmsuaW5jbHVkZWQua2F0eWRpZC05Mi5sY2wuZGV2JA',
125+
});
126+
127+
const result = redirectToSignIn({ returnBackUrl });
128+
assert.equal(result, 'redirectAdapterValue');
129+
assert.ok(
130+
redirectAdapterSpy.calledWith(
131+
`https://accounts.included.katydid-92.lcl.dev/sign-in?redirect_url=${encodedUrl}`,
132+
),
133+
);
134+
});
135+
136+
test('returns path based url with production publishableKey but without signInUrl to redirectToSignIn', assert => {
137+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
138+
const { redirectToSignIn } = redirect({
139+
redirectAdapter: redirectAdapterSpy,
140+
publishableKey: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k',
141+
});
142+
143+
const result = redirectToSignIn({ returnBackUrl });
144+
assert.equal(result, 'redirectAdapterValue');
145+
assert.ok(redirectAdapterSpy.calledWith(`https://accounts.example.com/sign-in?redirect_url=${encodedUrl}`));
146+
});
147+
148+
test('returns path based url with development (kima) publishableKey but without signInUrl to redirectToSignIn', assert => {
149+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
150+
const { redirectToSignIn } = redirect({
151+
redirectAdapter: redirectAdapterSpy,
152+
publishableKey: 'pk_test_aW5jbHVkZWQua2F0eWRpZC05Mi5jbGVyay5hY2NvdW50cy5kZXYk',
153+
});
154+
155+
const result = redirectToSignIn({ returnBackUrl });
156+
assert.equal(result, 'redirectAdapterValue');
157+
assert.ok(
158+
redirectAdapterSpy.calledWith(`https://included.katydid-92.accounts.dev/sign-in?redirect_url=${encodedUrl}`),
159+
);
160+
});
161+
162+
test('raises error with signUpUrl as relative path and returnBackUrl missing', assert => {
163+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
164+
const { redirectToSignUp } = redirect({ redirectAdapter: redirectAdapterSpy, signUpUrl: '/sign-up' });
165+
166+
assert.raises(
167+
() => redirectToSignUp(),
168+
new Error('destination url or return back url should be an absolute path url!'),
169+
);
170+
});
171+
172+
test('raises error with signUpUrl as relative path and returnBackUrl relative path', assert => {
173+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
174+
const { redirectToSignUp } = redirect({ redirectAdapter: redirectAdapterSpy, signUpUrl: '/sign-up' });
175+
176+
assert.raises(
177+
() => redirectToSignUp({ returnBackUrl: '/example' }),
178+
new Error('destination url or return back url should be an absolute path url!'),
179+
);
180+
});
181+
182+
test('returns path based url with signUpUrl as absolute path and returnBackUrl missing', assert => {
183+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
184+
const { redirectToSignUp } = redirect({
185+
redirectAdapter: redirectAdapterSpy,
186+
signUpUrl: 'http://signin.url:3001/sign-up',
187+
});
188+
189+
const result = redirectToSignUp();
190+
assert.equal(result, 'redirectAdapterValue');
191+
assert.ok(redirectAdapterSpy.calledWith(`http://signin.url:3001/sign-up`));
192+
});
193+
194+
test('returns path based url with signUpUrl as relative path', assert => {
195+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
196+
const { redirectToSignUp } = redirect({ redirectAdapter: redirectAdapterSpy, signUpUrl: '/sign-up' });
197+
198+
const result = redirectToSignUp({ returnBackUrl });
199+
assert.equal(result, 'redirectAdapterValue');
200+
assert.ok(redirectAdapterSpy.calledWith(`http://current.url:3000/sign-up?redirect_url=${encodedUrl}`));
201+
});
202+
203+
test('returns path based url with signUpUrl as absolute path', assert => {
204+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
205+
const { redirectToSignUp } = redirect({
206+
redirectAdapter: redirectAdapterSpy,
207+
signUpUrl: 'http://signup.url:3001/sign-up',
208+
});
209+
210+
const result = redirectToSignUp({ returnBackUrl });
211+
assert.equal(result, 'redirectAdapterValue');
212+
assert.ok(redirectAdapterSpy.calledWith(`http://signup.url:3001/sign-up?redirect_url=${encodedUrl}`));
213+
});
214+
215+
test('raises error without signUpUrl and frontendApi/publishableKey in redirectToSignUp', assert => {
216+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
217+
const { redirectToSignUp } = redirect({ redirectAdapter: redirectAdapterSpy });
218+
219+
assert.raises(
220+
() => redirectToSignUp({ returnBackUrl }),
221+
new Error('signUpUrl or frontendApi/publishableKey should be provided!'),
222+
);
223+
});
224+
225+
test('returns path based url with development frontendApi but without signUpUrl to redirectToSignUp', assert => {
226+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
227+
const { redirectToSignUp } = redirect({
228+
redirectAdapter: redirectAdapterSpy,
229+
frontendApi: 'clerk.included.katydid-92.lcl.dev',
230+
});
231+
232+
const result = redirectToSignUp({ returnBackUrl });
233+
assert.equal(result, 'redirectAdapterValue');
234+
assert.ok(
235+
redirectAdapterSpy.calledWith(
236+
`https://accounts.included.katydid-92.lcl.dev/sign-up?redirect_url=${encodedUrl}`,
237+
),
238+
);
239+
});
240+
241+
test('returns path based url with production frontendApi but without signUpUrl to redirectToSignUp', assert => {
242+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
243+
const { redirectToSignUp } = redirect({ redirectAdapter: redirectAdapterSpy, frontendApi: 'clerk.example.com' });
244+
245+
const result = redirectToSignUp({ returnBackUrl });
246+
assert.equal(result, 'redirectAdapterValue');
247+
assert.ok(redirectAdapterSpy.calledWith(`https://accounts.example.com/sign-up?redirect_url=${encodedUrl}`));
248+
});
249+
250+
test('returns path based url with development (kima) frontendApi but without signUpUrl to redirectToSignUp', assert => {
251+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
252+
const { redirectToSignUp } = redirect({
253+
redirectAdapter: redirectAdapterSpy,
254+
frontendApi: 'included.katydid-92.clerk.accounts.dev',
255+
});
256+
257+
const result = redirectToSignUp({ returnBackUrl });
258+
assert.equal(result, 'redirectAdapterValue');
259+
assert.ok(
260+
redirectAdapterSpy.calledWith(`https://included.katydid-92.accounts.dev/sign-up?redirect_url=${encodedUrl}`),
261+
);
262+
});
263+
264+
test('returns path based url with development publishableKey but without signUpUrl to redirectToSignUp', assert => {
265+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
266+
const { redirectToSignUp } = redirect({
267+
redirectAdapter: redirectAdapterSpy,
268+
publishableKey: 'pk_test_Y2xlcmsuaW5jbHVkZWQua2F0eWRpZC05Mi5sY2wuZGV2JA',
269+
});
270+
271+
const result = redirectToSignUp({ returnBackUrl });
272+
assert.equal(result, 'redirectAdapterValue');
273+
assert.ok(
274+
redirectAdapterSpy.calledWith(
275+
`https://accounts.included.katydid-92.lcl.dev/sign-up?redirect_url=${encodedUrl}`,
276+
),
277+
);
278+
});
279+
280+
test('returns path based url with production publishableKey but without signUpUrl to redirectToSignUp', assert => {
281+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
282+
const { redirectToSignUp } = redirect({
283+
redirectAdapter: redirectAdapterSpy,
284+
publishableKey: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k',
285+
});
286+
287+
const result = redirectToSignUp({ returnBackUrl });
288+
assert.equal(result, 'redirectAdapterValue');
289+
assert.ok(redirectAdapterSpy.calledWith(`https://accounts.example.com/sign-up?redirect_url=${encodedUrl}`));
290+
});
291+
292+
test('returns path based url with development (kima) publishableKey but without signUpUrl to redirectToSignUp', assert => {
293+
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
294+
const { redirectToSignUp } = redirect({
295+
redirectAdapter: redirectAdapterSpy,
296+
publishableKey: 'pk_test_aW5jbHVkZWQua2F0eWRpZC05Mi5jbGVyay5hY2NvdW50cy5kZXYk',
297+
});
298+
299+
const result = redirectToSignUp({ returnBackUrl });
300+
assert.equal(result, 'redirectAdapterValue');
301+
assert.ok(
302+
redirectAdapterSpy.calledWith(`https://included.katydid-92.accounts.dev/sign-up?redirect_url=${encodedUrl}`),
303+
);
304+
});
305+
});
306+
};

0 commit comments

Comments
 (0)