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

Commit a820074

Browse files
committed
feat: Integrate the auth_oauth2 module with the new auth module
1 parent baa98ff commit a820074

File tree

23 files changed

+544
-439
lines changed

23 files changed

+544
-439
lines changed

modules/auth/module.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"email": {},
1717
"users": {},
1818
"rate_limit": {},
19-
"tokens": {}
19+
"tokens": {},
20+
"auth_oauth2": {}
2021
},
2122
"scripts": {
2223
"get_flow_status": {
@@ -28,10 +29,6 @@
2829
"name": "Cancel Flow",
2930
"description": "Cancels a login flow. This is irreversible and will error if the flow is not `pending`."
3031
},
31-
"complete_flow": {
32-
"name": "Complete Flow",
33-
"description": "Completes a login flow and generates a user token. This is irreversible and will error if the flow is not `pending`."
34-
},
3532
"list_providers": {
3633
"name": "Send Email Verification",
3734
"description": "Send a one-time verification code to a user's email address to authenticate them.",

modules/auth/scripts/complete_flow.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import { Empty, RuntimeError, ScriptContext } from "../module.gen.ts";
2+
import { createFlowToken } from "../utils/flow.ts";
3+
import { initFlowWithProvider } from "../utils/providers.ts";
24
import { Provider } from "../utils/types.ts";
35

46
export interface Request {
57
provider: Provider;
68
}
79
export interface Response {
810
urlForLoginLink: string;
11+
token: string;
912
}
1013

1114
export async function run(
1215
ctx: ScriptContext,
1316
req: Request,
1417
): Promise<Response> {
15-
throw new RuntimeError("todo", { statusCode: 500 });
18+
const token = await createFlowToken(ctx, req.provider);
19+
const url = await initFlowWithProvider(ctx, token.token, req.provider);
20+
21+
return { token: token.token, urlForLoginLink: url };
1622
}

modules/auth/utils/flow.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { RuntimeError, ScriptContext } from "../module.gen.ts";
2+
import { pollProvider } from "./providers.ts";
3+
import { Provider } from "./types.ts";
24

35
/**
46
* The token type that designates that this is a flow token
@@ -30,11 +32,11 @@ function getExpiryTime() {
3032
* @returns A flow token (TokenWithSecret) with the correct meta and expiry
3133
* time.
3234
*/
33-
export async function createFlowToken(ctx: ScriptContext) {
35+
export async function createFlowToken(ctx: ScriptContext, provider: Provider) {
3436
const { token } = await ctx.modules.tokens.create({
3537
type: FLOW_TYPE,
36-
meta: {},
37-
expireAt: getExpiryTime().toString(),
38+
meta: { provider },
39+
expireAt: getExpiryTime().toISOString(),
3840
});
3941
return token;
4042
}
@@ -69,14 +71,23 @@ export async function getFlowStatus(
6971
return { status: "cancelled" };
7072
} else if (expireDate.getTime() <= Date.now()) {
7173
return { status: "expired" };
72-
} else if (!flowData.meta.userToken) {
73-
return { status: "pending" };
74-
} else {
74+
} else if (flowData.meta.userToken) {
7575
return {
7676
status: "complete",
7777
userToken: flowData.meta.userToken.toString(),
7878
};
7979
}
80+
81+
const provider = flowData.meta.provider;
82+
const pollResult = await pollProvider(ctx, flowToken, provider);
83+
if (pollResult) {
84+
return {
85+
status: "complete",
86+
userToken: pollResult,
87+
};
88+
} else {
89+
return { status: "pending" };
90+
}
8091
}
8192

8293
export async function cancelFlow(
@@ -106,6 +117,7 @@ export async function completeFlow(
106117
ctx: ScriptContext,
107118
flowToken: string,
108119
userId: string,
120+
additionalData: unknown,
109121
): Promise<string> {
110122
const status = await getFlowStatus(ctx, flowToken);
111123
switch (status.status) {
@@ -124,6 +136,12 @@ export async function completeFlow(
124136
token: flowToken,
125137
newMeta: { userToken: token.token },
126138
});
139+
await ctx.modules.tokens.modifyMeta({
140+
token: token.token,
141+
newMeta: {
142+
data: additionalData,
143+
},
144+
});
127145

128146
return token.token;
129147
}

modules/auth/utils/providers.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { RuntimeError, ScriptContext } from "../module.gen.ts";
2+
import { completeFlow } from "./flow.ts";
3+
import { OAuthProvider, Provider, ProviderType } from "./types.ts";
4+
5+
function getAuthProviderType(provider: Provider): ProviderType {
6+
if (ProviderType.EMAIL in provider) {
7+
return ProviderType.EMAIL;
8+
} else if (ProviderType.OAUTH in provider) {
9+
console.log("Provider is oauth:", provider);
10+
return ProviderType.OAUTH;
11+
} else {
12+
throw new RuntimeError("invalid_provider");
13+
}
14+
}
15+
16+
export async function initFlowWithProvider(
17+
ctx: ScriptContext,
18+
flowToken: string,
19+
provider: Provider,
20+
): Promise<string> {
21+
switch (getAuthProviderType(provider)) {
22+
case ProviderType.EMAIL:
23+
throw new Error("todo");
24+
25+
case ProviderType.OAUTH: {
26+
const { urlForLoginLink } = await ctx.modules.authOauth2.initFlow({
27+
flowToken,
28+
providerIdent: (provider as OAuthProvider).oauth,
29+
});
30+
return urlForLoginLink;
31+
}
32+
}
33+
}
34+
35+
export async function pollProvider(
36+
ctx: ScriptContext,
37+
flowToken: string,
38+
provider: Provider,
39+
): Promise<string | null> {
40+
switch (getAuthProviderType(provider)) {
41+
case ProviderType.EMAIL:
42+
throw new Error("todo");
43+
44+
case ProviderType.OAUTH: {
45+
const { details } = await ctx.modules.authOauth2.getLoginData({
46+
flowToken,
47+
providerIdent: (provider as OAuthProvider).oauth,
48+
});
49+
if (!details) return null;
50+
51+
const identity = await ctx.db.identityOAuth.findFirst({
52+
where: {
53+
subId: details.sub,
54+
provider: details.provider,
55+
},
56+
});
57+
if (!identity) throw new Error("todo");
58+
59+
const userToken = await completeFlow(
60+
ctx,
61+
flowToken,
62+
identity.userId,
63+
details.retainedTokenDetails,
64+
);
65+
66+
return userToken;
67+
}
68+
}
69+
}

modules/auth/utils/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Module } from "../module.gen.ts";
2+
13
export interface FlowToken {
24
token: string;
35
}
@@ -16,6 +18,9 @@ export type EmailProvider = Record<
1618
ProviderType.EMAIL,
1719
{ passwordless: boolean }
1820
>;
19-
export type OAuthProvider = Record<ProviderType.OAUTH, { provider: string }>;
21+
export type OAuthProvider = Record<
22+
ProviderType.OAUTH,
23+
Module.authOauth2.ProviderIdentifierDetails
24+
>;
2025

2126
export type Provider = EmailProvider | OAuthProvider;

modules/auth_oauth2/config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export interface Config {
2-
providers: Record<string, ProviderEndpoints | string>;
2+
providers: Record<string, ProviderEndpoints | string>;
33
}
44

55
export interface ProviderEndpoints {
6-
authorization: string;
7-
token: string;
8-
userinfo: string;
9-
scopes: string;
10-
userinfoKey: string;
6+
authorization: string;
7+
token: string;
8+
userinfo: string;
9+
scopes: string;
10+
userinfoKey: string;
1111
}

modules/auth_oauth2/module.json

Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,61 @@
11
{
2-
"name": "OAuth2 Authentication Provider",
3-
"description": "Authenticate users with OAuth 2.0.",
4-
"icon": "key",
5-
"tags": [
6-
"core",
7-
"user",
8-
"auth"
9-
],
10-
"authors": [
11-
"rivet-gg",
12-
"Skyler Calaman"
13-
],
14-
"status": "beta",
15-
"dependencies": {
16-
"rate_limit": {},
17-
"users": {},
18-
"tokens": {}
19-
},
20-
"routes": {
21-
"login_link": {
22-
"name": "Login Link",
23-
"description": "Generate a login link for accessing OpenGB.",
24-
"method": "GET",
25-
"pathPrefix": "/login/"
26-
},
27-
"login_callback": {
28-
"name": "OAuth Redirect Callback",
29-
"description": "Verify a user's OAuth login and create a session.",
30-
"method": "GET",
31-
"pathPrefix": "/callback/"
32-
}
33-
},
34-
"scripts": {},
35-
"errors": {
36-
"already_friends": {
37-
"name": "Already Friends"
38-
},
39-
"friend_request_not_found": {
40-
"name": "Friend Request Not Found"
41-
},
42-
"friend_request_already_exists": {
43-
"name": "Friend Request Already Exists"
44-
},
45-
"not_friend_request_recipient": {
46-
"name": "Not Friend Request Recipient"
47-
},
48-
"friend_request_already_accepted": {
49-
"name": "Friend Request Already Accepted"
50-
},
51-
"friend_request_already_declined": {
52-
"name": "Friend Request Already Declined"
53-
},
54-
"cannot_send_to_self": {
55-
"name": "Cannot Send to Self"
56-
}
57-
}
58-
}
2+
"name": "OAuth2 Authentication Provider",
3+
"description": "Authenticate users with OAuth 2.0.",
4+
"icon": "key",
5+
"tags": [
6+
"core",
7+
"user",
8+
"auth"
9+
],
10+
"authors": [
11+
"rivet-gg",
12+
"Skyler Calaman"
13+
],
14+
"status": "beta",
15+
"dependencies": {
16+
"rate_limit": {},
17+
"users": {},
18+
"tokens": {}
19+
},
20+
"routes": {
21+
"login_callback": {
22+
"name": "OAuth Redirect Callback",
23+
"description": "Verify a user's OAuth login and create a session.",
24+
"method": "GET",
25+
"pathPrefix": "/callback/"
26+
}
27+
},
28+
"scripts": {
29+
"init_flow": {
30+
"name": "Initialize Auth Flow",
31+
"description": "Update flow token for OAuth login and generate an authorization URI."
32+
},
33+
"get_login_data": {
34+
"name": "Get Login Data",
35+
"description": "Update flow token for OAuth login and generate an authorization URI."
36+
}
37+
},
38+
"errors": {
39+
"already_friends": {
40+
"name": "Already Friends"
41+
},
42+
"friend_request_not_found": {
43+
"name": "Friend Request Not Found"
44+
},
45+
"friend_request_already_exists": {
46+
"name": "Friend Request Already Exists"
47+
},
48+
"not_friend_request_recipient": {
49+
"name": "Not Friend Request Recipient"
50+
},
51+
"friend_request_already_accepted": {
52+
"name": "Friend Request Already Accepted"
53+
},
54+
"friend_request_already_declined": {
55+
"name": "Friend Request Already Declined"
56+
},
57+
"cannot_send_to_self": {
58+
"name": "Cannot Send to Self"
59+
}
60+
}
61+
}

modules/auth_oauth2/public.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { ProviderIdentifierDetails } from "./utils/types.ts";

0 commit comments

Comments
 (0)