mirror of https://github.com/TZGyn/shortener
add google oauth
parent
feaf0f3be6
commit
0cd7de59a1
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
ALTER TABLE "user" ALTER COLUMN "password" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "google_id" varchar(255);
|
||||
@ -0,0 +1,419 @@
|
||||
{
|
||||
"id": "9ba9c83e-b94c-42c5-84c9-010050810026",
|
||||
"prevId": "ac377af2-68ea-4c36-a69c-4cc57a04a523",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.email_verification_token": {
|
||||
"name": "email_verification_token",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"uuid": {
|
||||
"name": "uuid",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"qr_background": {
|
||||
"name": "qr_background",
|
||||
"type": "varchar(7)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'#ffffff'"
|
||||
},
|
||||
"qr_foreground": {
|
||||
"name": "qr_foreground",
|
||||
"type": "varchar(7)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'#000000'"
|
||||
},
|
||||
"domain_status": {
|
||||
"name": "domain_status",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'verified'"
|
||||
},
|
||||
"enable_custom_domain": {
|
||||
"name": "enable_custom_domain",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"custom_ip": {
|
||||
"name": "custom_ip",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"custom_domain_id": {
|
||||
"name": "custom_domain_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"custom_domain": {
|
||||
"name": "custom_domain",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.setting": {
|
||||
"name": "setting",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"qr_background": {
|
||||
"name": "qr_background",
|
||||
"type": "varchar(7)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"qr_foreground": {
|
||||
"name": "qr_foreground",
|
||||
"type": "varchar(7)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"public.shortener": {
|
||||
"name": "shortener",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"link": {
|
||||
"name": "link",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ios": {
|
||||
"name": "ios",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"ios_link": {
|
||||
"name": "ios_link",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"android": {
|
||||
"name": "android",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"android_link": {
|
||||
"name": "android_link",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"shortener_code_unique": {
|
||||
"name": "shortener_code_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"uuid": {
|
||||
"name": "uuid",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email_verified": {
|
||||
"name": "email_verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"google_id": {
|
||||
"name": "google_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"public.visitor": {
|
||||
"name": "visitor",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"shortener_id": {
|
||||
"name": "shortener_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"city": {
|
||||
"name": "city",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"device_type": {
|
||||
"name": "device_type",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"device_vendor": {
|
||||
"name": "device_vendor",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"os": {
|
||||
"name": "os",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"browser": {
|
||||
"name": "browser",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { SVGAttributes } from 'svelte/elements'
|
||||
|
||||
type $$Props = SVGAttributes<SVGElement>
|
||||
</script>
|
||||
|
||||
<svg role="img" viewBox="0 0 24 24" {...$$restProps}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
|
||||
</svg>
|
||||
@ -0,0 +1,36 @@
|
||||
import { google } from '$lib/server/auth'
|
||||
import { generateState, generateCodeVerifier } from 'arctic'
|
||||
import { serializeCookie } from 'oslo/cookie'
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const GET = async (event) => {
|
||||
const state = generateState()
|
||||
const codeVerifier = generateCodeVerifier()
|
||||
|
||||
const url = await google.createAuthorizationURL(
|
||||
state,
|
||||
codeVerifier,
|
||||
{
|
||||
scopes: ['email', 'profile'],
|
||||
},
|
||||
)
|
||||
|
||||
event.cookies.set('google_oauth_state', state, {
|
||||
httpOnly: true,
|
||||
secure: env.APP_ENV === 'prod',
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
})
|
||||
|
||||
event.cookies.set('google_oauth_code_verifier', codeVerifier, {
|
||||
httpOnly: true,
|
||||
secure: env.APP_ENV === 'prod',
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
})
|
||||
|
||||
return redirect(302, url.toString())
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
import { OAuth2RequestError } from 'arctic'
|
||||
import { google, lucia } from '$lib/server/auth'
|
||||
import { db } from '$lib/db'
|
||||
import { user } from '$lib/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
interface GoogleUser {
|
||||
sub: string // Unique identifier for the user
|
||||
name: string // Full name of the user
|
||||
email: string // Email address of the user
|
||||
}
|
||||
|
||||
export async function GET(event) {
|
||||
const code = event.url.searchParams.get('code')
|
||||
const state = event.url.searchParams.get('state')
|
||||
const codeVerifier = event.cookies.get('google_oauth_code_verifier')
|
||||
const storedState = event.cookies.get('google_oauth_state') ?? null
|
||||
|
||||
if (
|
||||
!code ||
|
||||
!state ||
|
||||
!storedState ||
|
||||
!codeVerifier ||
|
||||
state !== storedState
|
||||
) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const tokens = await google.validateAuthorizationCode(
|
||||
code,
|
||||
codeVerifier,
|
||||
)
|
||||
const googleUserResponse = await fetch(
|
||||
'https://www.googleapis.com/oauth2/v3/userinfo',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
},
|
||||
)
|
||||
const googleUser: GoogleUser = await googleUserResponse.json()
|
||||
|
||||
const existingGoogleUser = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.googleId, googleUser.sub),
|
||||
})
|
||||
|
||||
if (existingGoogleUser) {
|
||||
const session = await lucia.createSession(
|
||||
existingGoogleUser.id,
|
||||
{},
|
||||
)
|
||||
const sessionCookie = lucia.createSessionCookie(session.id)
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes,
|
||||
})
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/dashboard',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const existingUser = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.email, googleUser.email),
|
||||
})
|
||||
|
||||
if (existingUser) {
|
||||
const updateUser = await db
|
||||
.update(user)
|
||||
.set({
|
||||
email_verified: true,
|
||||
password: null,
|
||||
username: googleUser.name,
|
||||
})
|
||||
.where(eq(user.id, existingUser.id))
|
||||
.returning()
|
||||
|
||||
const newUser = updateUser[0]
|
||||
|
||||
const session = await lucia.createSession(newUser.id, {})
|
||||
const sessionCookie = lucia.createSessionCookie(session.id)
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes,
|
||||
})
|
||||
} else {
|
||||
const insertUser = await db
|
||||
.insert(user)
|
||||
.values({
|
||||
email: googleUser.email, // Using email as username
|
||||
email_verified: true,
|
||||
googleId: googleUser.sub,
|
||||
username: googleUser.name, // Name field may not always be present, handle accordingly
|
||||
})
|
||||
.returning()
|
||||
|
||||
const newUser = insertUser[0]
|
||||
|
||||
const session = await lucia.createSession(newUser.id, {})
|
||||
const sessionCookie = lucia.createSessionCookie(session.id)
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes,
|
||||
})
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/dashboard',
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/login',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue