diff --git a/.gitignore b/.gitignore index f974287..f133745 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,9 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store -*.sqlite \ No newline at end of file +# Tables +*.sqlite + +# Key files +private.key +public.pem \ No newline at end of file diff --git a/bun.lock b/bun.lock index b4e6370..42564a3 100644 --- a/bun.lock +++ b/bun.lock @@ -6,11 +6,13 @@ "dependencies": { "better-sqlite3": "^12.2.0", "drizzle-orm": "^0.44.2", + "openid-client": "^6.6.2", }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/bun": "latest", "@types/node": "^24.0.7", + "@types/openid-client": "^3.7.0", "drizzle-kit": "^0.31.4", }, "peerDependencies": { @@ -81,6 +83,8 @@ "@types/node": ["@types/node@24.0.7", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw=="], + "@types/openid-client": ["@types/openid-client@3.7.0", "", { "dependencies": { "openid-client": "*" } }, "sha512-hW+QbAeUyfUHABwUjUECOcv56Mf5tN29xK3GPN7wy3R3XfIQdkm37XELA4wbe2RNG9GYUpN8l2ytfoW05XmpgQ=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "better-sqlite3": ["better-sqlite3@12.2.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ=="], @@ -131,6 +135,8 @@ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -143,8 +149,12 @@ "node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="], + "oauth4webapi": ["oauth4webapi@3.5.5", "", {}, "sha512-1K88D2GiAydGblHo39NBro5TebGXa+7tYoyIbxvqv3+haDDry7CBE1eSYuNbOSsYCCU6y0gdynVZAkm4YPw4hg=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "openid-client": ["openid-client@6.6.2", "", { "dependencies": { "jose": "^6.0.11", "oauth4webapi": "^3.5.4" } }, "sha512-Xya5TNMnnZuTM6DbHdB4q0S3ig2NTAELnii/ASie1xDEr8iiB8zZbO871OWBdrw++sd3hW6bqWjgcmSy1RTWHA=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], diff --git a/package.json b/package.json index c3e2792..559f295 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/bun": "latest", "@types/node": "^24.0.7", + "@types/openid-client": "^3.7.0", "drizzle-kit": "^0.31.4" }, "peerDependencies": { @@ -14,7 +15,8 @@ }, "dependencies": { "better-sqlite3": "^12.2.0", - "drizzle-orm": "^0.44.2" + "drizzle-orm": "^0.44.2", + "openid-client": "^6.6.2" }, "scripts": { "dev": "bun --hot src/index.ts", diff --git a/src/auth/google/client.ts b/src/auth/google/client.ts new file mode 100644 index 0000000..ca64b25 --- /dev/null +++ b/src/auth/google/client.ts @@ -0,0 +1,56 @@ +import * as client from "openid-client"; + +const GOOGLE_ISSUER = new URL("https://accounts.google.com"); + +let OIDC: client.Configuration; +let code_verifier: string; +let state: string; + +// setup OIDC client +async function initOIDC() { + OIDC = await client.discovery( + GOOGLE_ISSUER, + process.env.GOOGLE_CLIENT_ID!, + process.env.GOOGLE_CLIENT_SECRET! + ); +} + +// start the login flow +export async function login() { + if (!OIDC) await initOIDC(); + + code_verifier = client.randomPKCECodeVerifier(); + const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); + state = client.randomState(); + + const params = { + redirect_uri: + process.env.GOOGLE_REDIRECT_URI || + "http://localhost:3000/auth/google/callback", + scope: "openid email profile", + code_challenge, + code_challenge_method: "S256" as const, + state, + }; + + // return the redirect URL + return client.buildAuthorizationUrl(OIDC, params); +} + +// handle the callback from google after login +export async function callback(url: URL) { + if (!OIDC) await initOIDC(); + + try { + const tokenSet = await client.authorizationCodeGrant(OIDC, url, { + pkceCodeVerifier: code_verifier, + expectedState: state, + }); + + const claims = tokenSet.claims(); + return claims; + } catch (error) { + console.error("Token exchange failed:", error); + return JSON.stringify({ error: "Authentication failed" }); + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..e2bf753 --- /dev/null +++ b/src/index.html @@ -0,0 +1,11 @@ + + +
+