diff --git a/bun.lock b/bun.lock index ef6ac29c..801bcfae 100644 --- a/bun.lock +++ b/bun.lock @@ -61,6 +61,7 @@ "@aws-sdk/client-s3": "^3.806.0", "@aws-sdk/client-sqs": "^3.806.0", "@nestri/core": "workspace:", + "@openauthjs/openauth": "catalog:", "actor-core": "^0.8.0", "hono": "^4.7.8", "hono-openapi": "^0.4.8", @@ -69,6 +70,7 @@ "steamid": "^2.1.0", }, "devDependencies": { + "@cloudflare/workers-types": "4.20250522.0", "@types/bun": "latest", "@types/steamcommunity": "^3.43.8", }, @@ -178,8 +180,8 @@ "@rocicorp/zero-sqlite3", "protobufjs", ], - "overrides": { - "@openauthjs/openauth": "0.4.3", + "catalog": { + "@openauthjs/openauth": "0.0.0-20250322224806", "steam-session": "1.9.3", }, "packages": { @@ -3517,6 +3519,10 @@ "@multiformats/dns/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "@nestri/functions/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250522.0", "", {}, "sha512-9RIffHobc35JWeddzBguGgPa4wLDr5x5F94+0/qy7LiV6pTBQ/M5qGEN9VA16IDT3EUpYI0WKh6VpcmeVEtVtw=="], + + "@nestri/functions/@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], + "@npmcli/agent/agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -3533,6 +3539,8 @@ "@openauthjs/openevent/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@openauthjs/solid/@openauthjs/openauth": ["@openauthjs/openauth@0.4.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-8+Bia559iffrZXfQ0LWXrVVVriochS88pDtB8indyQ1S+40MQgDBu8aBzKt+fgSrTmoQGCTT+wlOXgbjc9qIcw=="], + "@opentelemetry/auto-instrumentations-node/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="], @@ -4435,6 +4443,10 @@ "@multiformats/dns/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "@nestri/functions/@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + + "@openauthjs/solid/@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@opentelemetry/auto-instrumentations-node/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.33.0", "", {}, "sha512-TIpZvE8fiEILFfTlfPnltpBaD3d9/+uQHVCyC3vfdh6WfCXKhNFzoP5RyDDIndfvZC5GrA4pyEDNyjPloJud+w=="], diff --git a/cloud/infra/auth.ts b/cloud/infra/auth.ts new file mode 100644 index 00000000..e69de29b diff --git a/cloud/packages/functions/package.json b/cloud/packages/functions/package.json index 54d3fbec..178fb6fb 100644 --- a/cloud/packages/functions/package.json +++ b/cloud/packages/functions/package.json @@ -5,7 +5,8 @@ "private": true, "devDependencies": { "@types/bun": "latest", - "@types/steamcommunity": "^3.43.8" + "@types/steamcommunity": "^3.43.8", + "@cloudflare/workers-types": "4.20250522.0" }, "peerDependencies": { "typescript": "^5" diff --git a/cloud/packages/functions/src/auth/index.ts b/cloud/packages/functions/src/auth/index.ts index 7a968684..45d37314 100644 --- a/cloud/packages/functions/src/auth/index.ts +++ b/cloud/packages/functions/src/auth/index.ts @@ -1,159 +1,99 @@ +import { Select } from "./ui"; import { Resource } from "sst"; import { logger } from "hono/logger"; import { subjects } from "../subjects"; -import { handle } from "hono/aws-lambda"; -import { PasswordUI, Select } from "./ui"; +import { handleDiscord } from "./utils"; +import { DiscordAdapter } from "./adapters"; import { issuer } from "@openauthjs/openauth"; import { User } from "@nestri/core/user/index"; -import { Email } from "@nestri/core/email/index"; import { patchLogger } from "../utils/patch-logger"; -import { handleDiscord, handleGithub } from "./utils"; -import { DiscordAdapter, PasswordAdapter, GithubAdapter } from "./adapters"; +import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"; + +interface Env { + AuthStorage: KVNamespace; +} patchLogger(); - -const app = issuer({ - select: Select(), - theme: { +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + return issuer({ + select: Select(), + theme: { title: "Nestri | Auth", primary: "#FF4F01", //TODO: Change this in prod logo: "https://nestri.io/logo.webp", favicon: "https://nestri.io/seo/favicon.ico", background: { - light: "#F5F5F5", - dark: "#171717" + light: "#F5F5F5", + dark: "#171717", }, radius: "lg", font: { - family: "Geist, sans-serif", + family: "Geist, sans-serif", }, css: `@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100;200;300;400;500;600;700;800;900&display=swap');`, - }, - subjects, - providers: { - github: GithubAdapter({ - clientID: Resource.GithubClientID.value, - clientSecret: Resource.GithubClientSecret.value, - scopes: ["user:email"] - }), + }, + subjects, + storage: CloudflareStorage({ + namespace: env.AuthStorage, + }), + providers: { discord: DiscordAdapter({ - clientID: Resource.DiscordClientID.value, - clientSecret: Resource.DiscordClientSecret.value, - scopes: ["email", "identify"] + clientID: Resource.DiscordClientID.value, + clientSecret: Resource.DiscordClientSecret.value, + scopes: ["email", "identify"], }), - password: PasswordAdapter( - PasswordUI({ - sendCode: async (email, code) => { - // Do not debug show code in production - if (Resource.App.stage != "production") { - console.log("email & code:", email, code) - } - await Email.send( - "auth", - email, - `Nestri code: ${code}`, - `Your Nestri login code is ${code}`, - ) - }, - }), - ), - - }, - allow: async (input) => { + }, + allow: async (input) => { const url = new URL(input.redirectURI); const hostname = url.hostname; if (hostname.endsWith("nestri.io")) return true; if (hostname === "localhost") return true; return false; - }, - success: async (ctx, value, req) => { - if (value.provider === "password") { - const email = value.email - const username = value.username - const matching = await User.fromEmail(email) - - //Sign Up - if (username && !matching) { - const userID = await User.create({ - name: username, - email, - }); - - if (!userID) throw new Error("Error creating user"); - - return ctx.subject("user", { - userID, - email - }, { - subject: userID - }); - - } else if (matching) { - await User.acknowledgeLogin(matching.id) - - //Sign In - return ctx.subject("user", { - userID: matching.id, - email - }, { - subject: matching.id - }); - } - } - + }, + success: async (ctx, value, req) => { let user; - if (value.provider === "github") { - const access = value.tokenset.access; - user = await handleGithub(access) - } - if (value.provider === "discord") { - const access = value.tokenset.access - user = await handleDiscord(access) + const access = value.tokenset.access; + user = await handleDiscord(access); } if (user) { - try { - const matching = await User.fromEmail(user.primary.email); + try { + const matching = await User.fromEmail(user.primary.email); - //Sign Up - if (!matching) { - const userID = await User.create({ - email: user.primary.email, - name: user.username, - avatarUrl: user.avatar, - }); + //Sign Up + if (!matching) { + const userID = await User.create({ + email: user.primary.email, + name: user.username, + avatarUrl: user.avatar, + }); - if (!userID) throw new Error("Error creating user"); + if (!userID) throw new Error("Error creating user"); - return ctx.subject("user", { - userID, - email: user.primary.email - }, { - subject: userID - }); - } else { - await User.acknowledgeLogin(matching.id) + return ctx.subject("user", userID, { + userID, + email: user.primary.email, + }); + } else { + await User.acknowledgeLogin(matching.id); - //Sign In - return await ctx.subject("user", { - userID: matching.id, - email: user.primary.email - }, { - subject: matching.id - }); - } - - } catch (error) { - console.error("error registering the user", error) + //Sign In + return await ctx.subject("user", matching.id, { + userID: matching.id, + email: user.primary.email, + }); } - + } catch (error) { + console.error("error registering the user", error); + } } throw new Error("Something went seriously wrong"); - }, -}).use(logger()) - -export const handler = handle(app); \ No newline at end of file + }, + }).use(logger()); + }, +}; diff --git a/cloud/packages/functions/tsconfig.json b/cloud/packages/functions/tsconfig.json index c829fea7..7b53a409 100644 --- a/cloud/packages/functions/tsconfig.json +++ b/cloud/packages/functions/tsconfig.json @@ -5,6 +5,7 @@ "jsx": "react-jsx", "moduleResolution": "bundler", "strict": true, - "noUncheckedIndexedAccess": true + "noUncheckedIndexedAccess": true, + "types": ["@cloudflare/workers-types", "node"] } }