From a0b01e5adebad55156bea962431af40fdea434eb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 17 Sep 2024 17:32:26 -0700 Subject: [PATCH] Fixing OAuth flows, passing code and state correctly to hono, and signing in with GitHub. --- .../api/controllers/oauth.controller.ts | 85 ++++++++++------- src/lib/server/api/index.ts | 1 + .../repositories/collections.repository.ts | 8 +- .../repositories/credentials.repository.ts | 2 +- .../federated_identity.repository.ts | 25 ++--- .../api/repositories/roles.repository.ts | 10 +- .../api/repositories/user_roles.repository.ts | 4 +- .../api/services/collections.service.ts | 47 ++++++---- src/lib/server/api/services/oauth.service.ts | 60 ++++-------- .../server/api/services/user_roles.service.ts | 48 ++++++---- src/lib/server/api/services/users.service.ts | 91 ++++++++++++++----- .../server/api/services/wishlists.service.ts | 43 +++++---- .../(auth)/auth/callback/github/+server.ts | 42 +++++---- src/routes/(auth)/login/+page.server.ts | 3 + src/routes/(auth)/login/+page.svelte | 69 +++++++------- src/routes/(auth)/login/github/+server.ts | 14 +-- 16 files changed, 319 insertions(+), 233 deletions(-) diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts index 4b1bcb4..570f42b 100644 --- a/src/lib/server/api/controllers/oauth.controller.ts +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -4,52 +4,73 @@ import { LuciaService } from '$lib/server/api/services/lucia.service' import { OAuthService } from '$lib/server/api/services/oauth.service' import { github } from '$lib/server/auth' import { OAuth2RequestError } from 'arctic' -import { getCookie } from 'hono/cookie' +import { getCookie, setCookie } from 'hono/cookie' +import { TimeSpan } from 'oslo' import { inject, injectable } from 'tsyringe' @injectable() export class OAuthController extends Controller { constructor( @inject(LuciaService) private luciaService: LuciaService, - @inject(OAuthService) private readonly oauthService: OAuthService) { + @inject(OAuthService) private oauthService: OAuthService, + ) { super() } routes() { - return this.controller - .get('/github', async (c) => { - try { - const code = c.req.query('code')?.toString() ?? null - const state = c.req.query('state')?.toString() ?? null - const storedState = getCookie(c).github_oauth_state ?? null + return this.controller.get('/github', async (c) => { + try { + const code = c.req.query('code')?.toString() ?? null + const state = c.req.query('state')?.toString() ?? null + const storedState = getCookie(c).github_oauth_state ?? null - if (!code || !state || !storedState || state !== storedState) { - return c.body(null, 400) - } + console.log('code', code, 'state', state, 'storedState', storedState) - const tokens = await github.validateAuthorizationCode(code) - const githubUserResponse = await fetch("https://api.github.com/user", { - headers: { - Authorization: `Bearer ${tokens.accessToken}` - } - }); - const githubUser: GitHubUser = await githubUserResponse.json(); - - const token = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') - return c.json({ token }) - } catch (error) { - // the specific error message depends on the provider - if (error instanceof OAuth2RequestError) { - // invalid code - return c.body(null, 400) - } - return c.body(null, 500) + if (!code || !state || !storedState || state !== storedState) { + return c.body(null, 400) } - }) + + const tokens = await github.validateAuthorizationCode(code) + const githubUserResponse = await fetch('https://api.github.com/user', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + const githubUser: GitHubUser = await githubUserResponse.json() + + const userId = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + + const session = await this.luciaService.lucia.createSession(userId, {}) + const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) + + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + + return c.json({ message: 'ok' }) + } catch (error) { + console.error(error) + // the specific error message depends on the provider + if (error instanceof OAuth2RequestError) { + // invalid code + return c.body(null, 400) + } + return c.body(null, 500) + } + }) } } interface GitHubUser { - id: number; - login: string; -} \ No newline at end of file + id: number + login: string +} diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index f39f32a..3106d86 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,6 +1,7 @@ import 'reflect-metadata' import { CollectionController } from '$lib/server/api/controllers/collection.controller' import { MfaController } from '$lib/server/api/controllers/mfa.controller' +import { OAuthController } from '$lib/server/api/controllers/oauth.controller' import { SignupController } from '$lib/server/api/controllers/signup.controller' import { UserController } from '$lib/server/api/controllers/user.controller' import { WishlistController } from '$lib/server/api/controllers/wishlist.controller' diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts index bea63ed..c351714 100644 --- a/src/lib/server/api/repositories/collections.repository.ts +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -2,7 +2,7 @@ import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository' import { DrizzleService } from '$lib/server/api/services/drizzle.service' import { type InferInsertModel, eq } from 'drizzle-orm' import { inject, injectable } from 'tsyringe' -import { collection_items, collections } from '../databases/tables' +import { collections } from '../databases/tables' export type CreateCollection = InferInsertModel export type UpdateCollection = Partial @@ -61,10 +61,10 @@ export class CollectionsRepository { with: { collection_items: { columns: { - cuid: true - } + cuid: true, + }, }, - } + }, }) } diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 0671a40..bdd8de5 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -44,7 +44,7 @@ export class CredentialsRepository { } async findOneByIdOrThrow(id: string, db = this.drizzle.db) { - const credentials = await this.findOneById(id) + const credentials = await this.findOneById(id, db) if (!credentials) throw Error('Credentials not found') return credentials } diff --git a/src/lib/server/api/repositories/federated_identity.repository.ts b/src/lib/server/api/repositories/federated_identity.repository.ts index a67009e..2180590 100644 --- a/src/lib/server/api/repositories/federated_identity.repository.ts +++ b/src/lib/server/api/repositories/federated_identity.repository.ts @@ -1,25 +1,28 @@ -import { inject, injectable } from "tsyringe"; -import { DrizzleService } from "../services/drizzle.service"; -import { and, eq, type InferInsertModel } from "drizzle-orm"; -import { federatedIdentityTable } from "../databases/tables"; -import { takeFirstOrThrow } from "../common/utils/repository"; +import { type InferInsertModel, and, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository' +import { federatedIdentityTable } from '../databases/tables' +import { DrizzleService } from '../services/drizzle.service' export type CreateFederatedIdentity = InferInsertModel @injectable() export class FederatedIdentityRepository { - - constructor( - @inject(DrizzleService) private readonly drizzle: DrizzleService - ) { } + constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {} async findOneByUserIdAndProvider(userId: string, provider: string) { return this.drizzle.db.query.federatedIdentityTable.findFirst({ - where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)) + where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)), + }) + } + + async findOneByFederatedUserIdAndProvider(federatedUserId: string, provider: string) { + return this.drizzle.db.query.federatedIdentityTable.findFirst({ + where: and(eq(federatedIdentityTable.federated_user_id, federatedUserId), eq(federatedIdentityTable.identity_provider, provider)), }) } async create(data: CreateFederatedIdentity, db = this.drizzle.db) { return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts index 2750d5c..4d9f283 100644 --- a/src/lib/server/api/repositories/roles.repository.ts +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -28,29 +28,29 @@ export class RolesRepository { constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {} async findOneById(id: string, db = this.drizzle.db) { - return db.query.roles.findFirst({ + return db.query.rolesTable.findFirst({ where: eq(rolesTable.id, id), }) } async findOneByIdOrThrow(id: string, db = this.drizzle.db) { - const role = await this.findOneById(id) + const role = await this.findOneById(id, db) if (!role) throw Error('Role not found') return role } async findAll(db = this.drizzle.db) { - return db.query.roles.findMany() + return db.query.rolesTable.findMany() } async findOneByName(name: string, db = this.drizzle.db) { - return db.query.roles.findFirst({ + return db.query.rolesTable.findFirst({ where: eq(rolesTable.name, name), }) } async findOneByNameOrThrow(name: string, db = this.drizzle.db) { - const role = await this.findOneByName(name) + const role = await this.findOneByName(name, db) if (!role) throw Error('Role not found') return role } diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts index 737bc0c..ef95b74 100644 --- a/src/lib/server/api/repositories/user_roles.repository.ts +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -33,8 +33,8 @@ export class UserRolesRepository { }) } - async findOneByIdOrThrow(id: string) { - const userRole = await this.findOneById(id) + async findOneByIdOrThrow(id: string, db = this.drizzle.db) { + const userRole = await this.findOneById(id, db) if (!userRole) throw Error('User not found') return userRole } diff --git a/src/lib/server/api/services/collections.service.ts b/src/lib/server/api/services/collections.service.ts index 107ccdf..ccdbcbc 100644 --- a/src/lib/server/api/services/collections.service.ts +++ b/src/lib/server/api/services/collections.service.ts @@ -1,41 +1,50 @@ -import { inject, injectable } from "tsyringe"; -import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; -import { CollectionsRepository } from "../repositories/collections.repository"; +import type { db } from '$lib/server/api/packages/drizzle' +import { generateRandomAnimalName } from '$lib/utils/randomDataUtil' +import { inject, injectable } from 'tsyringe' +import { CollectionsRepository } from '../repositories/collections.repository' @injectable() export class CollectionsService { - constructor( - @inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository - ) { } + constructor(@inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository) {} async findOneByUserId(userId: string) { - return this.collectionsRepository.findOneByUserId(userId); + return this.collectionsRepository.findOneByUserId(userId) } async findAllByUserId(userId: string) { - return this.collectionsRepository.findAllByUserId(userId); + return this.collectionsRepository.findAllByUserId(userId) } async findAllByUserIdWithDetails(userId: string) { - return this.collectionsRepository.findAllByUserIdWithDetails(userId); + return this.collectionsRepository.findAllByUserIdWithDetails(userId) } async findOneById(id: string) { - return this.collectionsRepository.findOneById(id); + return this.collectionsRepository.findOneById(id) } async findOneByCuid(cuid: string) { - return this.collectionsRepository.findOneByCuid(cuid); + return this.collectionsRepository.findOneByCuid(cuid) } - async createEmptyNoName(userId: string) { - return this.createEmpty(userId, null); + async createEmptyNoName(userId: string, trx: Parameters[0]>[0] | null = null) { + return this.createEmpty(userId, null, trx) } - async createEmpty(userId: string, name: string | null) { - return this.collectionsRepository.create({ - user_id: userId, - name: name ?? generateRandomAnimalName(), - }); + async createEmpty(userId: string, name: string | null, trx: Parameters[0]>[0] | null = null) { + if (!trx) { + return this.collectionsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }) + } + + return this.collectionsRepository.create( + { + user_id: userId, + name: name ?? generateRandomAnimalName(), + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/oauth.service.ts b/src/lib/server/api/services/oauth.service.ts index 4432792..0e62baa 100644 --- a/src/lib/server/api/services/oauth.service.ts +++ b/src/lib/server/api/services/oauth.service.ts @@ -1,54 +1,26 @@ -import { github } from "$lib/server/auth"; -import { db } from "$lib/server/db"; -import { lucia } from "$lib/server/lucia"; -import type { RequestEvent } from "@sveltejs/kit"; -import { OAuth2RequestError } from "arctic"; -import { generateIdFromEntropySize } from "lucia"; -import { inject, injectable } from "tsyringe"; -import { UsersService } from "./users.service"; -import { FederatedIdentityRepository } from "../repositories/federated_identity.repository"; +import { inject, injectable } from 'tsyringe' +import { FederatedIdentityRepository } from '../repositories/federated_identity.repository' +import { UsersService } from './users.service' -@inejectable() +@injectable() export class OAuthService { constructor( @inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository, - @inject(UsersService) private readonly usersService: UsersService + @inject(UsersService) private readonly usersService: UsersService, ) {} async handleOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { - const existingUser = await this.federatedIdentityRepository.findOneByUserIdAndProvider(`${oauthUserId}`, oauthProvider) + const federatedUser = await this.federatedIdentityRepository.findOneByFederatedUserIdAndProvider(`${oauthUserId}`, oauthProvider) - if (existingUser) { - return existingUser.id - } else { - const userId = generateIdFromEntropySize(10); // 16 characters long - - const user = await this.drizzleService.db.transaction(async (trx) => { - const user = await this.usersService.create({ - id: userId, - username: oauthUsername - }, trx); - - await this.federatedIdentityRepository.create({ - identity_provider: oauthProvider, - user_id: userId, - identity_id: `${oauthUserId}` - }, trx); - return user; - }) - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: ".", - ...sessionCookie.attributes - }); + if (federatedUser) { + return federatedUser.user_id } - return new Response(null, { - status: 302, - headers: { - Location: "/" - } - }); + + const user = await this.usersService.createOAuthUser(oauthUserId, oauthUsername, oauthProvider) + + if (!user) { + throw new Error('Failed to create user') + } + return user.id } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/user_roles.service.ts b/src/lib/server/api/services/user_roles.service.ts index 04ad1d0..ea80eff 100644 --- a/src/lib/server/api/services/user_roles.service.ts +++ b/src/lib/server/api/services/user_roles.service.ts @@ -1,39 +1,51 @@ -import {inject, injectable} from "tsyringe"; -import {type CreateUserRole, UserRolesRepository} from "$lib/server/api/repositories/user_roles.repository"; -import {RolesService} from "$lib/server/api/services/roles.service"; +import type { db } from '$lib/server/api/packages/drizzle' +import { type CreateUserRole, UserRolesRepository } from '$lib/server/api/repositories/user_roles.repository' +import { RolesService } from '$lib/server/api/services/roles.service' +import { inject, injectable } from 'tsyringe' @injectable() export class UserRolesService { constructor( - @inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository, - @inject(RolesService) private readonly rolesService: RolesService - ) { } + @inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository, + @inject(RolesService) private readonly rolesService: RolesService, + ) {} async findOneById(id: string) { - return this.userRolesRepository.findOneById(id); + return this.userRolesRepository.findOneById(id) } async findAllByUserId(userId: string) { - return this.userRolesRepository.findAllByUserId(userId); + return this.userRolesRepository.findAllByUserId(userId) } async create(data: CreateUserRole) { - return this.userRolesRepository.create(data); + return this.userRolesRepository.create(data) } - async addRoleToUser(userId: string, roleName: string, primary = false) { + async addRoleToUser(userId: string, roleName: string, primary = false, trx: Parameters[0]>[0] | null = null) { // Find the role by its name - const role = await this.rolesService.findOneByNameOrThrow(roleName); + const role = await this.rolesService.findOneByNameOrThrow(roleName) if (!role || !role.id) { - throw new Error(`Role with name ${roleName} not found`); + throw new Error(`Role with name ${roleName} not found`) + } + + if (!trx) { + return this.userRolesRepository.create({ + user_id: userId, + role_id: role.id, + primary, + }) } // Create a UserRole entry linking the user and the role - return this.userRolesRepository.create({ - user_id: userId, - role_id: role.id, - primary, - }); + return this.userRolesRepository.create( + { + user_id: userId, + role_id: role.id, + primary, + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index f067809..008a41a 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -1,11 +1,14 @@ import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto' import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' +import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository' +import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository' import { TokensService } from '$lib/server/api/services/tokens.service' import { UserRolesService } from '$lib/server/api/services/user_roles.service' import { inject, injectable } from 'tsyringe' import { CredentialsType } from '../databases/tables' import { type UpdateUser, UsersRepository } from '../repositories/users.repository' import { CollectionsService } from './collections.service' +import { DrizzleService } from './drizzle.service' import { WishlistsService } from './wishlists.service' @injectable() @@ -13,9 +16,12 @@ export class UsersService { constructor( @inject(CollectionsService) private readonly collectionsService: CollectionsService, @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, + @inject(DrizzleService) private readonly drizzleService: DrizzleService, + @inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository, @inject(TokensService) private readonly tokenService: TokensService, @inject(UsersRepository) private readonly usersRepository: UsersRepository, @inject(UserRolesService) private readonly userRolesService: UserRolesService, + @inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository, @inject(WishlistsService) private readonly wishlistsService: WishlistsService, ) {} @@ -23,34 +29,71 @@ export class UsersService { const { firstName, lastName, email, username, password } = data const hashedPassword = await this.tokenService.createHashedToken(password) - const user = await this.usersRepository.create({ - first_name: firstName, - last_name: lastName, - email, - username, + return await this.drizzleService.db.transaction(async (trx) => { + const createdUser = await this.usersRepository.create( + { + first_name: firstName, + last_name: lastName, + email, + username, + }, + trx, + ) + + if (!createdUser) { + return null + } + + const credentials = await this.credentialsRepository.create( + { + user_id: createdUser.id, + type: CredentialsType.PASSWORD, + secret_data: hashedPassword, + }, + trx, + ) + + if (!credentials) { + await this.usersRepository.delete(createdUser.id) + return null + } + + await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + + await this.wishlistsService.createEmptyNoName(createdUser.id, trx) + await this.collectionsService.createEmptyNoName(createdUser.id, trx) }) + } - if (!user) { - return null - } + async createOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { + return await this.drizzleService.db.transaction(async (trx) => { + const createdUser = await this.usersRepository.create( + { + username: oauthUsername, + }, + trx, + ) - const credentials = await this.credentialsRepository.create({ - user_id: user.id, - type: CredentialsType.PASSWORD, - secret_data: hashedPassword, + if (!createdUser) { + return null + } + + await this.federatedIdentityRepository.create( + { + identity_provider: oauthProvider, + user_id: createdUser.id, + federated_user_id: `${oauthUserId}`, + federated_username: oauthUsername, + }, + trx, + ) + + await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + + await this.wishlistsService.createEmptyNoName(createdUser.id, trx) + await this.collectionsService.createEmptyNoName(createdUser.id, trx) + return createdUser }) - - if (!credentials) { - await this.usersRepository.delete(user.id) - return null - } - - await this.userRolesService.addRoleToUser(user.id, 'user', true) - - await this.wishlistsService.createEmptyNoName(user.id) - await this.collectionsService.createEmptyNoName(user.id) - - return user } async updateUser(userId: string, data: UpdateUser) { diff --git a/src/lib/server/api/services/wishlists.service.ts b/src/lib/server/api/services/wishlists.service.ts index f7326a1..b06b174 100644 --- a/src/lib/server/api/services/wishlists.service.ts +++ b/src/lib/server/api/services/wishlists.service.ts @@ -1,34 +1,41 @@ -import { inject, injectable } from "tsyringe"; -import { WishlistsRepository } from "../repositories/wishlists.repository"; -import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; +import type { db } from '$lib/server/api/packages/drizzle' +import { generateRandomAnimalName } from '$lib/utils/randomDataUtil' +import { inject, injectable } from 'tsyringe' +import { WishlistsRepository } from '../repositories/wishlists.repository' @injectable() export class WishlistsService { - - constructor( - @inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository - ) { } + constructor(@inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository) {} async findAllByUserId(userId: string) { - return this.wishlistsRepository.findAllByUserId(userId); + return this.wishlistsRepository.findAllByUserId(userId) } async findOneById(id: string) { - return this.wishlistsRepository.findOneById(id); + return this.wishlistsRepository.findOneById(id) } async findOneByCuid(cuid: string) { - return this.wishlistsRepository.findOneByCuid(cuid); + return this.wishlistsRepository.findOneByCuid(cuid) } - async createEmptyNoName(userId: string) { - return this.createEmpty(userId, null); + async createEmptyNoName(userId: string, trx: Parameters[0]>[0] | null = null) { + return this.createEmpty(userId, null, trx) } - async createEmpty(userId: string, name: string | null) { - return this.wishlistsRepository.create({ - user_id: userId, - name: name ?? generateRandomAnimalName(), - }); + async createEmpty(userId: string, name: string | null, trx: Parameters[0]>[0] | null = null) { + if (!trx) { + return this.wishlistsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }) + } + return this.wishlistsRepository.create( + { + user_id: userId, + name: name ?? generateRandomAnimalName(), + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/routes/(auth)/auth/callback/github/+server.ts b/src/routes/(auth)/auth/callback/github/+server.ts index c58847d..f4c0f73 100644 --- a/src/routes/(auth)/auth/callback/github/+server.ts +++ b/src/routes/(auth)/auth/callback/github/+server.ts @@ -1,21 +1,31 @@ -import { github } from "$lib/server/auth"; -import { OAuth2RequestError } from "arctic"; -import { generateIdFromEntropySize } from "lucia"; - -import type { RequestEvent } from "@sveltejs/kit"; +import { StatusCodes } from '$lib/constants/status-codes' +import type { RequestEvent } from '@sveltejs/kit' +import { redirect } from 'sveltekit-flash-message/server' export async function GET(event: RequestEvent): Promise { - const code = event.url.searchParams.get('code') - const state = event.url.searchParams.get('state') - const { data, error } = await locals.api.oauth.$get({ - params: { - code, - state - } - }) + const { locals, url } = event + const code = url.searchParams.get('code') + const state = url.searchParams.get('state') + console.log('code', code, 'state', state) + + const { data, error } = await locals.api.oauth.github.$get({ query: { code, state } }).then(locals.parseApiResponse) + + if (error) { + return new Response(JSON.stringify(error), { + status: 400, + }) + } + + if (!data) { + return new Response(JSON.stringify({ message: 'Invalid request' }), { + status: 400, + }) + } + + redirect(StatusCodes.TEMPORARY_REDIRECT, '/') } interface GitHubUser { - id: number; - login: string; -} \ No newline at end of file + id: number + login: string +} diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index a2b90e9..a08e5d1 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,3 +1,4 @@ +import { StatusCodes } from '$lib/constants/status-codes' import { signinUsernameDto } from '$lib/dtos/signin-username.dto' import { type Actions, fail } from '@sveltejs/kit' import { redirect } from 'sveltekit-flash-message/server' @@ -136,6 +137,8 @@ export const actions: Actions = { form.data.username = '' form.data.password = '' + redirect(StatusCodes.TEMPORARY_REDIRECT, '/') + // if ( // twoFactorDetails?.enabled && // twoFactorDetails?.secret !== null && diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 195f233..090ac27 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -1,41 +1,41 @@ @@ -63,6 +63,9 @@ +

Sign in

+Sign in with GitHub + {#snippet usernamePasswordForm()}
diff --git a/src/routes/(auth)/login/github/+server.ts b/src/routes/(auth)/login/github/+server.ts index a93f2f8..d9e5c33 100644 --- a/src/routes/(auth)/login/github/+server.ts +++ b/src/routes/(auth)/login/github/+server.ts @@ -2,17 +2,19 @@ import { github } from '$lib/server/auth' import { redirect } from '@sveltejs/kit' import { generateState } from 'arctic' -export async function GET(event) { - const state = generateState(); +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() const url = await github.createAuthorizationURL(state) - event.cookies.set('github_state', state, { + event.cookies.set('github_oauth_state', state, { path: '/', secure: import.meta.env.PROD, httpOnly: true, maxAge: 60 * 10, - sameSite: 'lax' - }); + sameSite: 'lax', + }) redirect(302, url.toString()) -} \ No newline at end of file +}