Skip to content
This repository was archived by the owner on Sep 17, 2024. It is now read-only.

Commit 1cace66

Browse files
committed
feat: Link the auth_oauth2 module to the auth_provider module
1 parent bcb307f commit 1cace66

File tree

7 files changed

+138
-1
lines changed

7 files changed

+138
-1
lines changed

modules/auth_oauth2/db/migrations/20240701012627_init/migration.sql renamed to modules/auth_oauth2/db/migrations/20240701041159_init/migration.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CREATE TABLE "LoginAttempts" (
55
"state" TEXT NOT NULL,
66
"codeVerifier" TEXT NOT NULL,
77
"identifier" TEXT,
8+
"tokenData" JSONB,
89
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
910
"expiresAt" TIMESTAMP(3) NOT NULL,
1011
"completedAt" TIMESTAMP(3),

modules/auth_oauth2/db/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ model LoginAttempts {
1212
codeVerifier String
1313
1414
identifier String?
15+
tokenData Json?
1516
1617
startedAt DateTime @default(now())
1718
expiresAt DateTime

modules/auth_oauth2/module.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
"name": "Get Status",
3737
"description": "Check the status of a OAuth login using the flow token. Returns the status of the login flow.",
3838
"public": true
39+
},
40+
"add_to_user": {
41+
"name": "Add OAuth Login to User",
42+
"description": "Use a finished OAuth flow to add the OAuth login to an already-authenticated users.",
43+
"public": true
44+
},
45+
"login_to_user": {
46+
"name": "Login to or Create User with OAuth",
47+
"description": "Use a finished OAuth flow to login to a user, creating a new one if it doesn't exist.",
48+
"public": true
3949
}
4050
},
4151
"errors": {

modules/auth_oauth2/routes/login_callback.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export async function handle(
8989
},
9090
data: {
9191
identifier: ident,
92+
tokenData: { ...tokens },
9293
completedAt: new Date(),
9394
},
9495
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { RuntimeError, ScriptContext } from "../module.gen.ts";
2+
3+
export interface Request {
4+
flowToken: string;
5+
userToken: string;
6+
}
7+
8+
export type Response = ReturnType<ScriptContext["modules"]["authProviders"]["addProviderToUser"]>;
9+
10+
export async function run(
11+
ctx: ScriptContext,
12+
req: Request,
13+
): Promise<Response> {
14+
await ctx.modules.rateLimit.throttlePublic({});
15+
16+
if (!req.flowToken) throw new RuntimeError("missing_token", { statusCode: 400 });
17+
18+
const { tokens: [flowToken] } = await ctx.modules.tokens.fetchByToken({ tokens: [req.flowToken] });
19+
if (!flowToken) {
20+
throw new RuntimeError("invalid_token", { statusCode: 400 });
21+
}
22+
if (new Date(flowToken.expireAt ?? 0) < new Date()) {
23+
throw new RuntimeError("expired_token", { statusCode: 400 });
24+
}
25+
26+
const flowId = flowToken.meta.flowId;
27+
if (!flowId) throw new RuntimeError("invalid_token", { statusCode: 400 });
28+
29+
const flow = await ctx.db.loginAttempts.findFirst({
30+
where: {
31+
id: flowId,
32+
}
33+
});
34+
if (!flow) throw new RuntimeError("invalid_token", { statusCode: 400 });
35+
36+
if (!flow.identifier || !flow.tokenData) {
37+
throw new RuntimeError("flow_not_complete", { statusCode: 400 });
38+
}
39+
40+
await ctx.modules.users.authenticateToken({ userToken: req.userToken });
41+
42+
const tokenData = flow.tokenData;
43+
if (!tokenData) {
44+
throw new RuntimeError("internal_error", { statusCode: 500 });
45+
}
46+
if (typeof tokenData !== "object") {
47+
throw new RuntimeError("internal_error", { statusCode: 500 });
48+
}
49+
if (Array.isArray(tokenData)) {
50+
throw new RuntimeError("internal_error", { statusCode: 500 });
51+
}
52+
53+
return await ctx.modules.authProviders.addProviderToUser({
54+
userToken: req.userToken,
55+
info: {
56+
providerType: "oauth2",
57+
providerId: flow.providerId,
58+
},
59+
uniqueData: {
60+
identifier: flow.identifier,
61+
},
62+
additionalData: tokenData,
63+
});
64+
}

modules/auth_oauth2/scripts/get_status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export async function run(
2929
});
3030
if (!flow) throw new RuntimeError("invalid_token", { statusCode: 400 });
3131

32-
if (flow.identifier) {
32+
if (flow.identifier && flow.tokenData) {
3333
return { status: "complete" };
3434
} else if (new Date(flow.expiresAt) < new Date()) {
3535
return { status: "expired" };
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { RuntimeError, ScriptContext } from "../module.gen.ts";
2+
3+
export interface Request {
4+
flowToken: string;
5+
}
6+
7+
export type Response = ReturnType<ScriptContext["modules"]["authProviders"]["getOrCreateUserFromProvider"]>;
8+
9+
export async function run(
10+
ctx: ScriptContext,
11+
req: Request,
12+
): Promise<Response> {
13+
await ctx.modules.rateLimit.throttlePublic({});
14+
15+
if (!req.flowToken) throw new RuntimeError("missing_token", { statusCode: 400 });
16+
17+
const { tokens: [flowToken] } = await ctx.modules.tokens.fetchByToken({ tokens: [req.flowToken] });
18+
if (!flowToken) {
19+
throw new RuntimeError("invalid_token", { statusCode: 400 });
20+
}
21+
if (new Date(flowToken.expireAt ?? 0) < new Date()) {
22+
throw new RuntimeError("expired_token", { statusCode: 400 });
23+
}
24+
25+
const flowId = flowToken.meta.flowId;
26+
if (!flowId) throw new RuntimeError("invalid_token", { statusCode: 400 });
27+
28+
const flow = await ctx.db.loginAttempts.findFirst({
29+
where: {
30+
id: flowId,
31+
}
32+
});
33+
if (!flow) throw new RuntimeError("invalid_token", { statusCode: 400 });
34+
35+
if (!flow.identifier || !flow.tokenData) {
36+
throw new RuntimeError("flow_not_complete", { statusCode: 400 });
37+
}
38+
39+
const tokenData = flow.tokenData;
40+
if (!tokenData) {
41+
throw new RuntimeError("internal_error", { statusCode: 500 });
42+
}
43+
if (typeof tokenData !== "object") {
44+
throw new RuntimeError("internal_error", { statusCode: 500 });
45+
}
46+
if (Array.isArray(tokenData)) {
47+
throw new RuntimeError("internal_error", { statusCode: 500 });
48+
}
49+
50+
return await ctx.modules.authProviders.getOrCreateUserFromProvider({
51+
info: {
52+
providerType: "oauth2",
53+
providerId: flow.providerId,
54+
},
55+
uniqueData: {
56+
identifier: flow.identifier,
57+
},
58+
additionalData: tokenData,
59+
});
60+
}

0 commit comments

Comments
 (0)