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

Commit

Permalink
feat: Integrate the auth_oauth2 module with the new auth module
Browse files Browse the repository at this point in the history
  • Loading branch information
Blckbrry-Pi committed Jul 2, 2024
1 parent 81cf4a9 commit 6c6bb47
Show file tree
Hide file tree
Showing 23 changed files with 544 additions and 439 deletions.
7 changes: 2 additions & 5 deletions modules/auth/module.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"email": {},
"users": {},
"rate_limit": {},
"tokens": {}
"tokens": {},
"auth_oauth2": {}
},
"scripts": {
"get_flow_status": {
Expand All @@ -28,10 +29,6 @@
"name": "Cancel Flow",
"description": "Cancels a login flow. This is irreversible and will error if the flow is not `pending`."
},
"complete_flow": {
"name": "Complete Flow",
"description": "Completes a login flow and generates a user token. This is irreversible and will error if the flow is not `pending`."
},
"list_providers": {
"name": "Send Email Verification",
"description": "Send a one-time verification code to a user's email address to authenticate them.",
Expand Down
19 changes: 0 additions & 19 deletions modules/auth/scripts/complete_flow.ts

This file was deleted.

8 changes: 7 additions & 1 deletion modules/auth/scripts/start_login_flow.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { Empty, RuntimeError, ScriptContext } from "../module.gen.ts";
import { createFlowToken } from "../utils/flow.ts";
import { initFlowWithProvider } from "../utils/providers.ts";
import { Provider } from "../utils/types.ts";

export interface Request {
provider: Provider;
}
export interface Response {
urlForLoginLink: string;
token: string;
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
throw new RuntimeError("todo", { statusCode: 500 });
const token = await createFlowToken(ctx, req.provider);
const url = await initFlowWithProvider(ctx, token.token, req.provider);

return { token: token.token, urlForLoginLink: url };
}
30 changes: 24 additions & 6 deletions modules/auth/utils/flow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RuntimeError, ScriptContext } from "../module.gen.ts";
import { pollProvider } from "./providers.ts";
import { Provider } from "./types.ts";

/**
* The token type that designates that this is a flow token
Expand Down Expand Up @@ -30,11 +32,11 @@ function getExpiryTime() {
* @returns A flow token (TokenWithSecret) with the correct meta and expiry
* time.
*/
export async function createFlowToken(ctx: ScriptContext) {
export async function createFlowToken(ctx: ScriptContext, provider: Provider) {
const { token } = await ctx.modules.tokens.create({
type: FLOW_TYPE,
meta: {},
expireAt: getExpiryTime().toString(),
meta: { provider },
expireAt: getExpiryTime().toISOString(),
});
return token;
}
Expand Down Expand Up @@ -69,14 +71,23 @@ export async function getFlowStatus(
return { status: "cancelled" };
} else if (expireDate.getTime() <= Date.now()) {
return { status: "expired" };
} else if (!flowData.meta.userToken) {
return { status: "pending" };
} else {
} else if (flowData.meta.userToken) {
return {
status: "complete",
userToken: flowData.meta.userToken.toString(),
};
}

const provider = flowData.meta.provider;
const pollResult = await pollProvider(ctx, flowToken, provider);
if (pollResult) {
return {
status: "complete",
userToken: pollResult,
};
} else {
return { status: "pending" };
}
}

export async function cancelFlow(
Expand Down Expand Up @@ -106,6 +117,7 @@ export async function completeFlow(
ctx: ScriptContext,
flowToken: string,
userId: string,
additionalData: unknown,
): Promise<string> {
const status = await getFlowStatus(ctx, flowToken);
switch (status.status) {
Expand All @@ -124,6 +136,12 @@ export async function completeFlow(
token: flowToken,
newMeta: { userToken: token.token },
});
await ctx.modules.tokens.modifyMeta({
token: token.token,
newMeta: {
data: additionalData,
},
});

return token.token;
}
69 changes: 69 additions & 0 deletions modules/auth/utils/providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { RuntimeError, ScriptContext } from "../module.gen.ts";
import { completeFlow } from "./flow.ts";
import { OAuthProvider, Provider, ProviderType } from "./types.ts";

function getAuthProviderType(provider: Provider): ProviderType {
if (ProviderType.EMAIL in provider) {
return ProviderType.EMAIL;
} else if (ProviderType.OAUTH in provider) {
console.log("Provider is oauth:", provider);
return ProviderType.OAUTH;
} else {
throw new RuntimeError("invalid_provider");
}
}

export async function initFlowWithProvider(
ctx: ScriptContext,
flowToken: string,
provider: Provider,
): Promise<string> {
switch (getAuthProviderType(provider)) {
case ProviderType.EMAIL:
throw new Error("todo");

case ProviderType.OAUTH: {
const { urlForLoginLink } = await ctx.modules.authOauth2.initFlow({
flowToken,
providerIdent: (provider as OAuthProvider).oauth,
});
return urlForLoginLink;
}
}
}

export async function pollProvider(
ctx: ScriptContext,
flowToken: string,
provider: Provider,
): Promise<string | null> {
switch (getAuthProviderType(provider)) {
case ProviderType.EMAIL:
throw new Error("todo");

case ProviderType.OAUTH: {
const { details } = await ctx.modules.authOauth2.getLoginData({
flowToken,
providerIdent: (provider as OAuthProvider).oauth,
});
if (!details) return null;

const identity = await ctx.db.identityOAuth.findFirst({
where: {
subId: details.sub,
provider: details.provider,
},
});
if (!identity) throw new Error("todo");

const userToken = await completeFlow(
ctx,
flowToken,
identity.userId,
details.retainedTokenDetails,
);

return userToken;
}
}
}
7 changes: 6 additions & 1 deletion modules/auth/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Module } from "../module.gen.ts";

export interface FlowToken {
token: string;
}
Expand All @@ -16,6 +18,9 @@ export type EmailProvider = Record<
ProviderType.EMAIL,
{ passwordless: boolean }
>;
export type OAuthProvider = Record<ProviderType.OAUTH, { provider: string }>;
export type OAuthProvider = Record<
ProviderType.OAUTH,
Module.authOauth2.ProviderIdentifierDetails
>;

export type Provider = EmailProvider | OAuthProvider;
12 changes: 6 additions & 6 deletions modules/auth_oauth2/config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export interface Config {
providers: Record<string, ProviderEndpoints | string>;
providers: Record<string, ProviderEndpoints | string>;
}

export interface ProviderEndpoints {
authorization: string;
token: string;
userinfo: string;
scopes: string;
userinfoKey: string;
authorization: string;
token: string;
userinfo: string;
scopes: string;
userinfoKey: string;
}
117 changes: 60 additions & 57 deletions modules/auth_oauth2/module.json
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
{
"name": "OAuth2 Authentication Provider",
"description": "Authenticate users with OAuth 2.0.",
"icon": "key",
"tags": [
"core",
"user",
"auth"
],
"authors": [
"rivet-gg",
"Skyler Calaman"
],
"status": "beta",
"dependencies": {
"rate_limit": {},
"users": {},
"tokens": {}
},
"routes": {
"login_link": {
"name": "Login Link",
"description": "Generate a login link for accessing OpenGB.",
"method": "GET",
"pathPrefix": "/login/"
},
"login_callback": {
"name": "OAuth Redirect Callback",
"description": "Verify a user's OAuth login and create a session.",
"method": "GET",
"pathPrefix": "/callback/"
}
},
"scripts": {},
"errors": {
"already_friends": {
"name": "Already Friends"
},
"friend_request_not_found": {
"name": "Friend Request Not Found"
},
"friend_request_already_exists": {
"name": "Friend Request Already Exists"
},
"not_friend_request_recipient": {
"name": "Not Friend Request Recipient"
},
"friend_request_already_accepted": {
"name": "Friend Request Already Accepted"
},
"friend_request_already_declined": {
"name": "Friend Request Already Declined"
},
"cannot_send_to_self": {
"name": "Cannot Send to Self"
}
}
}
"name": "OAuth2 Authentication Provider",
"description": "Authenticate users with OAuth 2.0.",
"icon": "key",
"tags": [
"core",
"user",
"auth"
],
"authors": [
"rivet-gg",
"Skyler Calaman"
],
"status": "beta",
"dependencies": {
"rate_limit": {},
"users": {},
"tokens": {}
},
"routes": {
"login_callback": {
"name": "OAuth Redirect Callback",
"description": "Verify a user's OAuth login and create a session.",
"method": "GET",
"pathPrefix": "/callback/"
}
},
"scripts": {
"init_flow": {
"name": "Initialize Auth Flow",
"description": "Update flow token for OAuth login and generate an authorization URI."
},
"get_login_data": {
"name": "Get Login Data",
"description": "Update flow token for OAuth login and generate an authorization URI."
}
},
"errors": {
"already_friends": {
"name": "Already Friends"
},
"friend_request_not_found": {
"name": "Friend Request Not Found"
},
"friend_request_already_exists": {
"name": "Friend Request Already Exists"
},
"not_friend_request_recipient": {
"name": "Not Friend Request Recipient"
},
"friend_request_already_accepted": {
"name": "Friend Request Already Accepted"
},
"friend_request_already_declined": {
"name": "Friend Request Already Declined"
},
"cannot_send_to_self": {
"name": "Cannot Send to Self"
}
}
}
1 change: 1 addition & 0 deletions modules/auth_oauth2/public.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { ProviderIdentifierDetails } from "./utils/types.ts";
Loading

0 comments on commit 6c6bb47

Please sign in to comment.