mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding OAuth for GitHub.
This commit is contained in:
parent
b1527e7782
commit
fbf4d08b07
17 changed files with 645 additions and 385 deletions
|
|
@ -18,6 +18,10 @@ ADMIN_PASSWORD=
|
||||||
|
|
||||||
TWO_FACTOR_TIMEOUT=300000
|
TWO_FACTOR_TIMEOUT=300000
|
||||||
|
|
||||||
|
# OAuth
|
||||||
|
GITHUB_CLIENT_ID=""
|
||||||
|
GITHUB_CLIENT_SECRET=""
|
||||||
|
|
||||||
# Public
|
# Public
|
||||||
|
|
||||||
PUBLIC_SITE_NAME='Bored Game'
|
PUBLIC_SITE_NAME='Bored Game'
|
||||||
|
|
|
||||||
16
package.json
16
package.json
|
|
@ -27,16 +27,17 @@
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@melt-ui/pp": "^0.3.2",
|
"@melt-ui/pp": "^0.3.2",
|
||||||
"@melt-ui/svelte": "^0.83.0",
|
"@melt-ui/svelte": "^0.83.0",
|
||||||
"@playwright/test": "^1.47.0",
|
"@playwright/test": "^1.47.1",
|
||||||
"@sveltejs/adapter-auto": "^3.2.4",
|
"@sveltejs/adapter-auto": "^3.2.4",
|
||||||
"@sveltejs/enhanced-img": "^0.3.4",
|
"@sveltejs/enhanced-img": "^0.3.7",
|
||||||
"@sveltejs/kit": "^2.5.26",
|
"@sveltejs/kit": "^2.5.27",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.5",
|
||||||
"@types/pg": "^8.11.9",
|
"@types/pg": "^8.11.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
|
"arctic": "^1.9.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"drizzle-kit": "^0.23.2",
|
"drizzle-kit": "^0.23.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
|
|
@ -47,7 +48,7 @@
|
||||||
"lucia": "3.2.0",
|
"lucia": "3.2.0",
|
||||||
"lucide-svelte": "^0.408.0",
|
"lucide-svelte": "^0.408.0",
|
||||||
"nodemailer": "^6.9.15",
|
"nodemailer": "^6.9.15",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.47",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-load-config": "^5.1.0",
|
"postcss-load-config": "^5.1.0",
|
||||||
"postcss-preset-env": "^9.6.0",
|
"postcss-preset-env": "^9.6.0",
|
||||||
|
|
@ -64,13 +65,13 @@
|
||||||
"svelte-sequential-preprocessor": "^2.0.1",
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
"sveltekit-flash-message": "^2.4.4",
|
"sveltekit-flash-message": "^2.4.4",
|
||||||
"sveltekit-rate-limiter": "^0.5.2",
|
"sveltekit-rate-limiter": "^0.5.2",
|
||||||
"sveltekit-superforms": "^2.17.0",
|
"sveltekit-superforms": "^2.18.0",
|
||||||
"tailwindcss": "^3.4.11",
|
"tailwindcss": "^3.4.11",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.7.0",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.1",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.4.4",
|
"vite": "^5.4.5",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
|
@ -92,7 +93,6 @@
|
||||||
"@sveltejs/adapter-vercel": "^5.4.3",
|
"@sveltejs/adapter-vercel": "^5.4.3",
|
||||||
"@types/feather-icons": "^4.29.4",
|
"@types/feather-icons": "^4.29.4",
|
||||||
"@vercel/og": "^0.5.20",
|
"@vercel/og": "^0.5.20",
|
||||||
"arctic": "^1.9.2",
|
|
||||||
"bits-ui": "^0.21.13",
|
"bits-ui": "^0.21.13",
|
||||||
"boardgamegeekclient": "^1.9.1",
|
"boardgamegeekclient": "^1.9.1",
|
||||||
"bullmq": "^5.13.0",
|
"bullmq": "^5.13.0",
|
||||||
|
|
|
||||||
752
pnpm-lock.yaml
752
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -17,6 +17,8 @@ const EnvSchema = z.object({
|
||||||
DATABASE_DB: z.string(),
|
DATABASE_DB: z.string(),
|
||||||
DB_MIGRATING: stringBoolean,
|
DB_MIGRATING: stringBoolean,
|
||||||
DB_SEEDING: stringBoolean,
|
DB_SEEDING: stringBoolean,
|
||||||
|
GITHUB_CLIENT_ID: z.string(),
|
||||||
|
GITHUB_CLIENT_SECRET: z.string(),
|
||||||
NODE_ENV: z.string().default('development'),
|
NODE_ENV: z.string().default('development'),
|
||||||
ORIGIN: z.string(),
|
ORIGIN: z.string(),
|
||||||
PUBLIC_SITE_NAME: z.string(),
|
PUBLIC_SITE_NAME: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ export class CollectionController extends Controller {
|
||||||
console.log('collections service', collections)
|
console.log('collections service', collections)
|
||||||
return c.json({ collections })
|
return c.json({ collections })
|
||||||
})
|
})
|
||||||
|
.get('/count', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user
|
||||||
|
const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id)
|
||||||
|
return c.json({ collections })
|
||||||
|
})
|
||||||
.get('/:cuid', requireAuth, async (c) => {
|
.get('/:cuid', requireAuth, async (c) => {
|
||||||
const cuid = c.req.param('cuid')
|
const cuid = c.req.param('cuid')
|
||||||
const collection = await this.collectionsService.findOneByCuid(cuid)
|
const collection = await this.collectionsService.findOneByCuid(cuid)
|
||||||
|
|
|
||||||
55
src/lib/server/api/controllers/oauth.controller.ts
Normal file
55
src/lib/server/api/controllers/oauth.controller.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import 'reflect-metadata'
|
||||||
|
import { Controller } from '$lib/server/api/common/types/controller'
|
||||||
|
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 { inject, injectable } from 'tsyringe'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OAuthController extends Controller {
|
||||||
|
constructor(
|
||||||
|
@inject(LuciaService) private luciaService: LuciaService,
|
||||||
|
@inject(OAuthService) private readonly 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
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubUser {
|
||||||
|
id: number;
|
||||||
|
login: string;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm'
|
||||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||||
import { timestamps } from '../../common/utils/table'
|
import { timestamps } from '../../common/utils/table'
|
||||||
import { usersTable } from './users.table'
|
import { usersTable } from './users.table'
|
||||||
|
import { collection_items } from './collectionItems.table'
|
||||||
|
|
||||||
export const collections = pgTable('collections', {
|
export const collections = pgTable('collections', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
|
@ -16,11 +17,12 @@ export const collections = pgTable('collections', {
|
||||||
...timestamps,
|
...timestamps,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const collection_relations = relations(collections, ({ one }) => ({
|
export const collection_relations = relations(collections, ({ one, many }) => ({
|
||||||
user: one(usersTable, {
|
user: one(usersTable, {
|
||||||
fields: [collections.user_id],
|
fields: [collections.user_id],
|
||||||
references: [usersTable.id],
|
references: [usersTable.id],
|
||||||
}),
|
}),
|
||||||
|
collection_items: many(collection_items),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export type Collections = InferSelectModel<typeof collections>
|
export type Collections = InferSelectModel<typeof collections>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ const routes = app
|
||||||
.route('/me', container.resolve(IamController).routes())
|
.route('/me', container.resolve(IamController).routes())
|
||||||
.route('/user', container.resolve(UserController).routes())
|
.route('/user', container.resolve(UserController).routes())
|
||||||
.route('/login', container.resolve(LoginController).routes())
|
.route('/login', container.resolve(LoginController).routes())
|
||||||
|
.route('/oauth', container.resolve(OAuthController).routes())
|
||||||
.route('/signup', container.resolve(SignupController).routes())
|
.route('/signup', container.resolve(SignupController).routes())
|
||||||
.route('/wishlists', container.resolve(WishlistController).routes())
|
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||||
.route('/collections', container.resolve(CollectionController).routes())
|
.route('/collections', container.resolve(CollectionController).routes())
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository'
|
||||||
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
import { DrizzleService } from '$lib/server/api/services/drizzle.service'
|
||||||
import { type InferInsertModel, eq } from 'drizzle-orm'
|
import { type InferInsertModel, eq } from 'drizzle-orm'
|
||||||
import { inject, injectable } from 'tsyringe'
|
import { inject, injectable } from 'tsyringe'
|
||||||
import { collections } from '../databases/tables'
|
import { collection_items, collections } from '../databases/tables'
|
||||||
|
|
||||||
export type CreateCollection = InferInsertModel<typeof collections>
|
export type CreateCollection = InferInsertModel<typeof collections>
|
||||||
export type UpdateCollection = Partial<CreateCollection>
|
export type UpdateCollection = Partial<CreateCollection>
|
||||||
|
|
@ -51,6 +51,23 @@ export class CollectionsRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findAllByUserIdWithDetails(userId: string, db = this.drizzle.db) {
|
||||||
|
return db.query.collections.findMany({
|
||||||
|
where: eq(collections.user_id, userId),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
with: {
|
||||||
|
collection_items: {
|
||||||
|
columns: {
|
||||||
|
cuid: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async create(data: CreateCollection, db = this.drizzle.db) {
|
async create(data: CreateCollection, db = this.drizzle.db) {
|
||||||
return db.insert(collections).values(data).returning().then(takeFirstOrThrow)
|
return db.insert(collections).values(data).returning().then(takeFirstOrThrow)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
export type CreateFederatedIdentity = InferInsertModel<typeof federatedIdentityTable>
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class FederatedIdentityRepository {
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: CreateFederatedIdentity, db = this.drizzle.db) {
|
||||||
|
return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ export class CollectionsService {
|
||||||
return this.collectionsRepository.findAllByUserId(userId);
|
return this.collectionsRepository.findAllByUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findAllByUserIdWithDetails(userId: string) {
|
||||||
|
return this.collectionsRepository.findAllByUserIdWithDetails(userId);
|
||||||
|
}
|
||||||
|
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.collectionsRepository.findOneById(id);
|
return this.collectionsRepository.findOneById(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
src/lib/server/api/services/oauth.service.ts
Normal file
54
src/lib/server/api/services/oauth.service.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
@inejectable()
|
||||||
|
export class OAuthService {
|
||||||
|
constructor(
|
||||||
|
@inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository,
|
||||||
|
@inject(UsersService) private readonly usersService: UsersService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handleOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) {
|
||||||
|
const existingUser = await this.federatedIdentityRepository.findOneByUserIdAndProvider(`${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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers: {
|
||||||
|
Location: "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/lib/server/auth.ts
Normal file
4
src/lib/server/auth.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import env from "$lib/server/api/common/env";
|
||||||
|
import { GitHub } from "arctic";
|
||||||
|
|
||||||
|
export const github = new GitHub(env.GITHUB_CLIENT_ID, env.GITHUB_CLIENT_SECRET);
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { notSignedInMessage } from '$lib/flashMessages'
|
import { notSignedInMessage } from '$lib/flashMessages'
|
||||||
|
import { collection_items, collections, gamesTable } from '$lib/server/api/databases/tables'
|
||||||
import { db } from '$lib/server/api/packages/drizzle'
|
import { db } from '$lib/server/api/packages/drizzle'
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils'
|
|
||||||
import { modifyListGameSchema } from '$lib/validations/zod-schemas'
|
import { modifyListGameSchema } from '$lib/validations/zod-schemas'
|
||||||
import { type Actions, error, fail } from '@sveltejs/kit'
|
import { type Actions, error, fail } from '@sveltejs/kit'
|
||||||
import { and, eq } from 'drizzle-orm'
|
import { and, eq } from 'drizzle-orm'
|
||||||
import { redirect } from 'sveltekit-flash-message/server'
|
import { redirect } from 'sveltekit-flash-message/server'
|
||||||
import { zod } from 'sveltekit-superforms/adapters'
|
import { zod } from 'sveltekit-superforms/adapters'
|
||||||
import { superValidate } from 'sveltekit-superforms/server'
|
import { superValidate } from 'sveltekit-superforms/server'
|
||||||
import { collection_items, collections, gamesTable } from '../../../../lib/server/api/databases/tables'
|
|
||||||
|
|
||||||
export async function load(event) {
|
export async function load(event) {
|
||||||
const { locals } = event
|
const { locals } = event
|
||||||
|
|
@ -18,23 +17,10 @@ export async function load(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userCollections = await db.query.collections.findMany({
|
const { data, error } = await locals.api.collections.$get().then(locals.parseApiResponse)
|
||||||
columns: {
|
|
||||||
cuid: true,
|
|
||||||
name: true,
|
|
||||||
created_at: true,
|
|
||||||
},
|
|
||||||
where: eq(collections.user_id, authedUser.id),
|
|
||||||
})
|
|
||||||
console.log('collections', userCollections)
|
|
||||||
|
|
||||||
if (userCollections?.length === 0) {
|
|
||||||
console.log('Collection was not found')
|
|
||||||
return fail(404, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collections: userCollections,
|
collections: data?.collections || [],
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,34 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
const { data } = $props();
|
import * as Card from '$components/ui/card'
|
||||||
let collections = data?.collections || [];
|
const { data } = $props()
|
||||||
|
let collections = data?.collections || []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Your Collections | Bored Game</title>
|
<title>Your Collections | Bored Game</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
<h1>Your Collections</h1>
|
<h1>Your Collections</h1>
|
||||||
|
|
||||||
<div class="collections">
|
|
||||||
<div class="collection-list">
|
<div class="collection-list">
|
||||||
{#if collections.length === 0}
|
{#if collections.length === 0}
|
||||||
<h2>You have no collections</h2>
|
<h2>You have no collections</h2>
|
||||||
{:else}
|
{:else}
|
||||||
{#each collections as collection}
|
{#each collections as collection}
|
||||||
<div class="collection grid gap-0.5">
|
<Card.Root>
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title>{collection.name}</Card.Title>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<p>Number of items:</p>
|
||||||
|
<p>Created at: {new Date(collection.createdAt).toLocaleString()}</p>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
<!-- <div class="collection grid gap-0.5">
|
||||||
<h2><a href="/collections/{collection.cuid}">{collection.name}</a></h2>
|
<h2><a href="/collections/{collection.cuid}">{collection.name}</a></h2>
|
||||||
<h3>Created at: {new Date(collection.created_at).toLocaleString()}</h3>
|
<h3>Created at: {new Date(collection.createdAt).toLocaleString()}</h3>
|
||||||
</div>
|
</div> -->
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -30,10 +40,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collections {
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collection-list {
|
.collection-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
||||||
|
|
|
||||||
21
src/routes/(auth)/auth/callback/github/+server.ts
Normal file
21
src/routes/(auth)/auth/callback/github/+server.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { github } from "$lib/server/auth";
|
||||||
|
import { OAuth2RequestError } from "arctic";
|
||||||
|
import { generateIdFromEntropySize } from "lucia";
|
||||||
|
|
||||||
|
import type { RequestEvent } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function GET(event: RequestEvent): Promise<Response> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubUser {
|
||||||
|
id: number;
|
||||||
|
login: string;
|
||||||
|
}
|
||||||
18
src/routes/(auth)/login/github/+server.ts
Normal file
18
src/routes/(auth)/login/github/+server.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { github } from '$lib/server/auth'
|
||||||
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
import { generateState } from 'arctic'
|
||||||
|
|
||||||
|
export async function GET(event) {
|
||||||
|
const state = generateState();
|
||||||
|
const url = await github.createAuthorizationURL(state)
|
||||||
|
|
||||||
|
event.cookies.set('github_state', state, {
|
||||||
|
path: '/',
|
||||||
|
secure: import.meta.env.PROD,
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 60 * 10,
|
||||||
|
sameSite: 'lax'
|
||||||
|
});
|
||||||
|
|
||||||
|
redirect(302, url.toString())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue