diff --git a/.env.example b/.env.example
index 1ab4b4dab..0d8ebdff3 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,5 @@
-UNDB_DB_TURSO_URL=libsql://undb-project-nichenqin.turso.io
-UNDB_DB_TURSO_AUTH_TOKEN=
\ No newline at end of file
+UNDB_DB_TURSO_URL=libsql://127.0.0.1:8080?tls=0
+UNDB_DB_TURSO_AUTH_TOKEN=
+
+GITHUB_CLIENT_ID=
+GITHUB_CLIENT_SECRET=
\ No newline at end of file
diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts
index b5bc6107c..a26c4a6eb 100644
--- a/apps/backend/src/app.ts
+++ b/apps/backend/src/app.ts
@@ -11,6 +11,7 @@ import { swagger } from "@elysiajs/swagger"
import { trpc } from "@elysiajs/trpc"
import { executionContext } from "@undb/context/server"
import { container } from "@undb/di"
+import { env } from "@undb/env"
import { Graphql } from "@undb/graphql"
import { createLogger } from "@undb/logger"
import { dbMigrate } from "@undb/persistence"
@@ -135,7 +136,7 @@ export const app = new Elysia()
if (!user) {
return context.redirect(`/login?redirect=${context.path}`, 301)
}
- if (!user.emailVerified && user.email) {
+ if (env.UNDB_VERIFY_EMAIL && !user.emailVerified && user.email) {
return context.redirect(`/verify-email?redirect=${context.path}`, 301)
}
},
diff --git a/apps/backend/src/modules/auth/auth.ts b/apps/backend/src/modules/auth/auth.ts
index 8dc7f4ba0..bb73fc508 100644
--- a/apps/backend/src/modules/auth/auth.ts
+++ b/apps/backend/src/modules/auth/auth.ts
@@ -191,7 +191,7 @@ export class Auth {
return ctx.redirect("/login")
}
- if (!user.emailVerified && user.email) {
+ if (env.UNDB_VERIFY_EMAIL && !user.emailVerified && user.email) {
return ctx.redirect(`/verify-email`, 301)
}
@@ -271,17 +271,19 @@ export class Auth {
await this.spaceMemberService.createMember(userId, space.id.value, "owner")
ctx.cookie[SPACE_ID_COOKIE_NAME].set({ value: space.id.value })
- const verificationCode = await this.#generateEmailVerificationCode(userId, email)
- await this.mailService.send({
- template: "verify-email",
- data: {
- username: username!,
- code: verificationCode,
- action_url: new URL(`/api/email-verification`, env.UNDB_BASE_URL).toString(),
- },
- subject: "Verify your email - undb",
- to: email,
- })
+ if (env.UNDB_VERIFY_EMAIL) {
+ const verificationCode = await this.#generateEmailVerificationCode(userId, email)
+ await this.mailService.send({
+ template: "verify-email",
+ data: {
+ username: username!,
+ code: verificationCode,
+ action_url: new URL(`/api/email-verification`, env.UNDB_BASE_URL).toString(),
+ },
+ subject: "Verify your email - undb",
+ to: email,
+ })
+ }
})
}
@@ -423,7 +425,7 @@ export class Auth {
)
.get("/login/github", async (ctx) => {
const state = generateState()
- const url = await github.createAuthorizationURL(state)
+ const url = await github.createAuthorizationURL(state, { scopes: ["user:email"] })
return new Response(null, {
status: 302,
headers: {
@@ -483,6 +485,56 @@ export class Auth {
})
}
+ const emailsResponse = await fetch("https://api.github.com/user/emails", {
+ headers: {
+ Authorization: `Bearer ${tokens.accessToken}`,
+ },
+ })
+ const emails: GithubEmail[] = await emailsResponse.json()
+
+ const primaryEmail = emails.find((email) => email.primary) ?? null
+ if (!primaryEmail) {
+ return new Response("No primary email address", {
+ status: 400,
+ })
+ }
+ if (!primaryEmail.verified) {
+ return new Response("Unverified email", {
+ status: 400,
+ })
+ }
+
+ const existingGithubUser = await this.queryBuilder
+ .selectFrom("undb_user")
+ .selectAll()
+ .where("undb_user.email", "=", primaryEmail.email)
+ .executeTakeFirst()
+
+ if (existingGithubUser) {
+ const spaceId = ctx.cookie[SPACE_ID_COOKIE_NAME].value
+ if (!spaceId) {
+ await this.spaceService.setSpaceContext(setContextValue, { userId: existingGithubUser.id })
+ }
+
+ await this.queryBuilder
+ .insertInto("undb_oauth_account")
+ .values({
+ provider_id: "github",
+ provider_user_id: githubUserResult.id.toString(),
+ user_id: existingGithubUser.id,
+ })
+ .execute()
+
+ const session = await lucia.createSession(existingGithubUser.id, {})
+ const sessionCookie = lucia.createSessionCookie(session.id)
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: "/",
+ "Set-Cookie": sessionCookie.serialize(),
+ },
+ })
+ }
const userId = generateIdFromEntropySize(10) // 16 characters long
await withTransaction(this.queryBuilder)(async () => {
const tx = getCurrentTransaction()
@@ -561,3 +613,9 @@ interface GitHubUserResult {
id: number
login: string // username
}
+
+interface GithubEmail {
+ email: string
+ primary: boolean
+ verified: boolean
+}
diff --git a/apps/frontend/src/lib/images/Google.svg b/apps/frontend/src/lib/images/Google.svg
new file mode 100644
index 000000000..2eaf91554
--- /dev/null
+++ b/apps/frontend/src/lib/images/Google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/frontend/src/lib/images/github.svg b/apps/frontend/src/lib/images/github.svg
index bc5d249d3..538ec5bf2 100644
--- a/apps/frontend/src/lib/images/github.svg
+++ b/apps/frontend/src/lib/images/github.svg
@@ -1,16 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/apps/frontend/src/routes/(auth)/login/+page.svelte b/apps/frontend/src/routes/(auth)/login/+page.svelte
index a87323e9c..35894afb4 100644
--- a/apps/frontend/src/routes/(auth)/login/+page.svelte
+++ b/apps/frontend/src/routes/(auth)/login/+page.svelte
@@ -4,6 +4,7 @@
import { Input } from "$lib/components/ui/input/index.js"
import { Label } from "$lib/components/ui/label/index.js"
import Logo from "$lib/images/logo.svg"
+ import Github from "$lib/images/github.svg"
import { createMutation } from "@tanstack/svelte-query"
import { z } from "@undb/zod"
import { defaults, superForm } from "sveltekit-superforms"
@@ -12,7 +13,6 @@
import { toast } from "svelte-sonner"
import { Button } from "$lib/components/ui/button"
import { Separator } from "$lib/components/ui/separator"
- import { GithubIcon } from "lucide-svelte"
const schema = z.object({
email: z.string().email(),
@@ -30,10 +30,6 @@
toast.error(error.message)
await goto("/signup")
},
- onSettled(data, error, variables, context) {
- console.log(data)
- console.log(error)
- },
})
const form = superForm(
@@ -112,9 +108,9 @@
Sign up