Skip to content

Commit 898ba5b

Browse files
committed
WIP
1 parent 61931bc commit 898ba5b

File tree

16 files changed

+1290
-3
lines changed

16 files changed

+1290
-3
lines changed

frontend/src/hooks.server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Handle } from "@sveltejs/kit";
2+
3+
export const handle: Handle = async ({ event, resolve }) => {
4+
if (event.url.pathname.startsWith("/__/auth/handler")) {
5+
console.log("This will be used as auth handler");
6+
}
7+
8+
const response = await resolve(event);
9+
return response;
10+
};

frontend/src/lib/components/auth/button/AuthProviderButton.svelte

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,86 @@
44
import GoogleIcon from "$lib/components/auth/GoogleIcon.svelte";
55
import GitlabIcon from "$lib/components/auth/GitlabIcon.svelte";
66
import GithubIcon from "$lib/components/auth/GithubIcon.svelte";
7+
import { AgentLabsApp } from "$lib/externals/agentlabs-js-sdk/agentlabs";
8+
import { getAuth, signInWithRedirect } from "$lib/externals/agentlabs-js-sdk/auth";
9+
import GoogleAuthProvider from "$lib/externals/agentlabs-js-sdk/auth-providers/google";
10+
import GitlabAuthProvider from "$lib/externals/agentlabs-js-sdk/auth-providers/gitlab";
11+
import GithubAuthProvider from "$lib/externals/agentlabs-js-sdk/auth-providers/github";
712
813
export let provider: AuthProvider;
914
10-
const providerIconMap = {
15+
let providerCurrentlyLoading: AuthProvider | null = null;
16+
17+
const providerIconMap: Record<
18+
AuthProvider,
19+
typeof GoogleIcon | typeof GitlabIcon | typeof GithubIcon
20+
> = {
1121
google: GoogleIcon,
1222
gitlab: GitlabIcon,
1323
github: GithubIcon
1424
};
25+
26+
const providerNameMap: Record<AuthProvider, string> = {
27+
google: "Google",
28+
gitlab: "Gitlab",
29+
github: "Github"
30+
};
31+
32+
// This will be instantiated in the main layout after fetching project config
33+
const app = new AgentLabsApp({
34+
project: {
35+
id: "",
36+
slug: "something",
37+
name: "something"
38+
},
39+
signInMethods: {
40+
google: {
41+
id: "google",
42+
isEnabled: true,
43+
oauthSettings: {
44+
clientId:
45+
"1046622402922-2q425h1v1dmacgg2p4g0bj89f8un67q3.apps.googleusercontent.com"
46+
}
47+
}
48+
}
49+
});
50+
51+
const auth = getAuth(app);
52+
53+
const providerHandlerMap: Record<AuthProvider, () => void> = {
54+
google: () =>
55+
signInWithRedirect(
56+
auth,
57+
new GoogleAuthProvider([], auth.getOAuthSignInMethods().google)
58+
),
59+
gitlab: () =>
60+
signInWithRedirect(
61+
auth,
62+
new GitlabAuthProvider([], auth.getOAuthSignInMethods().gitlab)
63+
),
64+
github: () =>
65+
signInWithRedirect(
66+
auth,
67+
new GithubAuthProvider([], auth.getOAuthSignInMethods().github)
68+
)
69+
};
70+
71+
const handleLogin = () => {
72+
console.log(auth.getOAuthSignInMethods().google);
73+
74+
providerCurrentlyLoading = provider;
75+
76+
providerHandlerMap[provider]();
77+
};
1578
</script>
1679

17-
<Button type="secondary" center={false}>
80+
<Button
81+
type="secondary"
82+
center={false}
83+
on:click={handleLogin}
84+
disabled={providerCurrentlyLoading === provider}>
1885
<div class="flex items-center gap-5">
1986
<svelte:component this={providerIconMap[provider]} />
20-
<span>Continue with Google</span>
87+
<span>Continue with {providerNameMap[provider]}</span>
2188
</div>
2289
</Button>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
export type InitConfig = {
2+
project: ProjectConfig;
3+
signInMethods: SignInMethods;
4+
};
5+
6+
export const SignInMethodIds = [
7+
"email-and-password",
8+
"magic-email-code",
9+
"google",
10+
"github",
11+
"gitlab",
12+
"microsoft",
13+
"twitter"
14+
] as const;
15+
16+
export type SignInMethodId = (typeof SignInMethodIds)[number];
17+
18+
export interface SignInMethodOAuthSettings {
19+
clientId: string;
20+
scopes?: string[];
21+
hasClientSecret: boolean;
22+
isDevMode?: boolean;
23+
}
24+
25+
export interface SignInMethod {
26+
id: SignInMethodId;
27+
isEnabled: boolean;
28+
settings?: Record<string, any>;
29+
oauthSettings?: SignInMethodOAuthSettings;
30+
}
31+
32+
export type SignInMethods = Partial<Record<SignInMethodId, SignInMethod>>;
33+
34+
export type PublicOAuthConfig = {
35+
signInMethods: SignInMethods;
36+
};
37+
38+
export type ProjectConfig = {
39+
id: string;
40+
slug: string;
41+
name: string;
42+
};
43+
44+
export class AgentLabsApp {
45+
constructor(protected config: InitConfig) {}
46+
47+
getProject(): ProjectConfig {
48+
return this.config.project;
49+
}
50+
51+
getOAuthConfig(): PublicOAuthConfig {
52+
return { signInMethods: this.config.signInMethods };
53+
}
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export const OAuthProviderIds = ['google', 'microsoft', 'github', 'gitlab', 'twitter'] as const;
2+
export type OAuthProviderId = (typeof OAuthProviderIds)[number];
3+
4+
export type AuthorizationParams = {
5+
client_id: string;
6+
redirect_uri: string;
7+
response_type: 'token' | 'code';
8+
scope: string;
9+
include_granted_scopes: string;
10+
access_type?: string;
11+
state: string;
12+
code_challenge?: string;
13+
code_challenge_method?: 'plain' | 'S256';
14+
};
15+
16+
export interface OAuthProvider {
17+
id: OAuthProviderId;
18+
addScope: (scope: string) => void;
19+
getScopes: () => string[];
20+
getAuthUrl: () => string;
21+
getResponseType: () => 'token' | 'code';
22+
getState: () => string;
23+
getCodeChallenge?: () => Promise<{
24+
codeChallenge: string;
25+
codeVerifier: string;
26+
codeChallengeMethod?: 'plain' | 'S256';
27+
}>;
28+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { unique } from "../utils/unique";
2+
import { randomState } from "../utils/randomState";
3+
import type { OAuthProvider, OAuthProviderId } from "./auth-providers.types";
4+
5+
export const GithubMinimumScopes = ["read:user", "user:email"] as const;
6+
7+
export const GithubPossibleScopes = [
8+
"repo",
9+
"repo_deployment",
10+
"repo:status",
11+
"public_repo",
12+
"repo:invite",
13+
"security_events",
14+
"admin:repo_hook",
15+
"write:repo_hook",
16+
"read:repo_hook",
17+
"admin:org",
18+
"write:org",
19+
"read:org",
20+
"admin:public_key",
21+
"write:public_key",
22+
"read:public_key",
23+
"admin:org_hook",
24+
"gist",
25+
"notifications",
26+
"user",
27+
"read:user",
28+
"user:email",
29+
"user:follow",
30+
"project",
31+
"read:project",
32+
"delete_repo",
33+
"write:packages",
34+
"read:packages",
35+
"delete:packages",
36+
"admin:gpg_key",
37+
"write:gpg_key",
38+
"read:gpg_key",
39+
"codespace",
40+
"workflow"
41+
] as const;
42+
43+
export type GithubScope = (typeof GithubPossibleScopes)[number];
44+
45+
export class GithubAuthProvider implements OAuthProvider {
46+
private scopes: string[] = [];
47+
48+
constructor(scopes: GithubScope[]) {
49+
const allScopes = [...scopes, ...GithubMinimumScopes];
50+
allScopes.forEach((scope) => this.addScope(scope));
51+
}
52+
53+
get id(): OAuthProviderId {
54+
return "github";
55+
}
56+
57+
addScope(scope: string): void {
58+
this.scopes.push(scope);
59+
}
60+
61+
getScopes(): string[] {
62+
return unique(this.scopes);
63+
}
64+
65+
getAuthUrl(): string {
66+
return "https://github.com/login/oauth/authorize";
67+
}
68+
69+
getResponseType(): "code" {
70+
return "code";
71+
}
72+
73+
getState(): string {
74+
return randomState();
75+
}
76+
}
77+
78+
export default GithubAuthProvider;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { unique } from "../utils/unique";
2+
import { randomState } from "../utils/randomState";
3+
import type { OAuthProvider, OAuthProviderId } from "./auth-providers.types";
4+
5+
export const GitlabMinimumScopes = ["openid", "profile", "email", "read_user"] as const;
6+
7+
export const GitlabPossibleScopes = [
8+
"api",
9+
"read_user",
10+
"read_api",
11+
"read_repository",
12+
"write_repository",
13+
"read_registry",
14+
"write_registry",
15+
"sudo",
16+
"openid",
17+
"profile",
18+
"email",
19+
"create_runner"
20+
];
21+
export type GitlabScope = (typeof GitlabPossibleScopes)[number];
22+
23+
export class GitlabAuthProvider implements OAuthProvider {
24+
private scopes: string[] = [];
25+
26+
constructor(scopes: GitlabScope[]) {
27+
const allScopes = [...scopes, ...GitlabMinimumScopes];
28+
allScopes.forEach((scope) => this.addScope(scope));
29+
}
30+
31+
get id(): OAuthProviderId {
32+
return "gitlab";
33+
}
34+
35+
addScope(scope: string): void {
36+
this.scopes.push(scope);
37+
}
38+
39+
getScopes(): string[] {
40+
return unique(this.scopes);
41+
}
42+
43+
getAuthUrl(): string {
44+
return "https://gitlab.com/oauth/authorize";
45+
}
46+
47+
getResponseType(): "code" {
48+
return "code";
49+
}
50+
51+
getState(): string {
52+
return randomState();
53+
}
54+
}
55+
56+
export default GitlabAuthProvider;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { unique } from "../utils/unique";
2+
import type { OAuthProvider, OAuthProviderId } from "./auth-providers.types";
3+
import type { AuthProviderSettings } from "../common.types";
4+
5+
const GoogleMinimumScopes = [
6+
"https://www.googleapis.com/auth/userinfo.email",
7+
"https://www.googleapis.com/auth/userinfo.profile"
8+
] as const;
9+
10+
// Full scopes list: https://developers.google.com/identity/protocols/oauth2/scopes
11+
// We don't add all scopes because some of them are too sensitive and we don't need them.
12+
const GooglePossibleScopes = [
13+
"https://www.googleapis.com/auth/userinfo.email",
14+
"https://www.googleapis.com/auth/userinfo.profile",
15+
"https://www.googleapis.com/auth/calendar",
16+
"https://www.googleapis.com/auth/youtube",
17+
"https://www.googleapis.com/auth/photoslibrary",
18+
"https://www.googleapis.com/auth/contacts",
19+
"https://www.googleapis.com/auth/drive.file",
20+
"https://www.googleapis.com/auth/spreadsheets",
21+
"https://www.googleapis.com/auth/presentations",
22+
"https://www.googleapis.com/auth/webmasters",
23+
"https://www.googleapis.com/auth/documents",
24+
"https://www.googleapis.com/auth/analytics.readonly",
25+
"https://www.googleapis.com/auth/gmail.modify",
26+
"https://www.googleapis.com/auth/calendar.events"
27+
] as const;
28+
29+
export type GoogleScope = (typeof GooglePossibleScopes)[number];
30+
31+
export class GoogleAuthProvider implements OAuthProvider {
32+
private scopes: string[] = [];
33+
private settings: AuthProviderSettings;
34+
35+
constructor(scopes: GoogleScope[], settings?: AuthProviderSettings) {
36+
if (!settings) {
37+
throw new Error("GoogleAuthProvider: settings is required");
38+
}
39+
40+
this.settings = settings;
41+
42+
const allScopes = [...scopes, ...GoogleMinimumScopes];
43+
allScopes.forEach((scope) => this.addScope(scope));
44+
}
45+
46+
get id(): OAuthProviderId {
47+
return "google";
48+
}
49+
50+
addScope(scope: string): void {
51+
this.scopes.push(scope);
52+
}
53+
54+
getScopes(): string[] {
55+
return unique(this.scopes);
56+
}
57+
58+
getAuthUrl(): string {
59+
return "https://accounts.google.com/o/oauth2/v2/auth";
60+
}
61+
62+
getResponseType(): "token" | "code" {
63+
if (this.settings.oauthSettings?.hasClientSecret) {
64+
return "code";
65+
}
66+
return "token";
67+
}
68+
69+
getState(): string {
70+
return "pass-through value";
71+
}
72+
}
73+
74+
export default GoogleAuthProvider;

0 commit comments

Comments
 (0)