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

feat: Integrate the auth_oauth2 module with the new auth module #119

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading