almost done fixing up oauth
This commit is contained in:
parent
3341ea2bf6
commit
093b7a8135
31 changed files with 677 additions and 520 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
89
package.json
89
package.json
|
@ -1,46 +1,47 @@
|
||||||
{
|
{
|
||||||
"name": "talkomatic",
|
"name": "talkomatic",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@deno/kv": "^0.8.1",
|
"@deno/kv": "^0.8.1",
|
||||||
"@hono/zod-validator": "^0.2.2",
|
"@hono/zod-validator": "^0.2.2",
|
||||||
"@olli/kvdex": "npm:@jsr/olli__kvdex",
|
"@olli/kvdex": "npm:@jsr/olli__kvdex",
|
||||||
"@oxi/option": "npm:@jsr/oxi__option",
|
"@oxi/option": "npm:@jsr/oxi__option",
|
||||||
"@oxi/result": "npm:@jsr/oxi__result",
|
"@oxi/result": "npm:@jsr/oxi__result",
|
||||||
"@petamoriken/float16": "^3.8.7",
|
"@petamoriken/float16": "^3.8.7",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
|
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
|
||||||
"arctic": "^1.9.2",
|
"arctic": "^1.9.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"hono": "^4.5.5",
|
"hono": "^4.5.5",
|
||||||
"nanoid": "^5.0.7",
|
"jose": "^5.6.3",
|
||||||
"oslo": "^1.2.1",
|
"nanoid": "^5.0.7",
|
||||||
"postcss": "^8.4.41",
|
"oslo": "^1.2.1",
|
||||||
"prettier": "^3.3.2",
|
"postcss": "^8.4.41",
|
||||||
"prettier-plugin-svelte": "^3.2.5",
|
"prettier": "^3.3.2",
|
||||||
"svelte": "^5.0.0-next.1",
|
"prettier-plugin-svelte": "^3.2.5",
|
||||||
"svelte-check": "^3.6.0",
|
"svelte": "^5.0.0-next.1",
|
||||||
"sveltekit-adapter-deno": "^0.12.1",
|
"svelte-check": "^3.6.0",
|
||||||
"sveltekit-superforms": "^2.17.0",
|
"sveltekit-adapter-deno": "^0.12.1",
|
||||||
"tailwindcss": "^3.4.10",
|
"sveltekit-superforms": "^2.17.0",
|
||||||
"typescript": "^5.0.0",
|
"tailwindcss": "^3.4.10",
|
||||||
"vite": "^5.0.3",
|
"typescript": "^5.0.0",
|
||||||
"zod": "^3.23.8"
|
"vite": "^5.0.3",
|
||||||
},
|
"zod": "^3.23.8"
|
||||||
"private": true,
|
},
|
||||||
"scripts": {
|
"private": true,
|
||||||
"dev": "vite dev",
|
"scripts": {
|
||||||
"build": "vite build",
|
"dev": "vite dev",
|
||||||
"preview": "vite preview",
|
"build": "vite build",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"preview": "vite preview",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"format": "prettier --write .",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "prettier --check ."
|
"format": "prettier --write .",
|
||||||
},
|
"lint": "prettier --check ."
|
||||||
"trustedDependencies": [
|
},
|
||||||
"svelte-preprocess"
|
"trustedDependencies": [
|
||||||
],
|
"svelte-preprocess"
|
||||||
"type": "module"
|
],
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/app.d.ts
vendored
12
src/app.d.ts
vendored
|
@ -1,9 +1,9 @@
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
|
||||||
import type { FlatDocumentData } from "@olli/kvdex"
|
import type { FlatDocumentData } from '@olli/kvdex'
|
||||||
import { user, session } from "$lib/server/db"
|
import { user, session } from '$lib/server/db'
|
||||||
import z from "zod"
|
import z from 'zod'
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
|
@ -12,10 +12,10 @@ declare global {
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
interface Locals {
|
interface Locals {
|
||||||
user: FlatDocumentData<z.infer<typeof user>, string> | null;
|
user: FlatDocumentData<z.infer<typeof user>, string> | null
|
||||||
session: FlatDocumentData<z.infer<typeof session>, string> | null;
|
session: FlatDocumentData<z.infer<typeof session>, string> | null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import { cookieController, cookieExpiration, getUserAndSession } from "$lib/server/auth";
|
import { cookieController, cookieExpiration, getUserAndSession } from '$lib/server/auth'
|
||||||
import type { Handle } from "@sveltejs/kit";
|
import type { Handle } from '@sveltejs/kit'
|
||||||
import { createDate } from "oslo";
|
import { createDate } from 'oslo'
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
const sessionId = event.cookies.get("auth_session");
|
const sessionId = event.cookies.get('auth_session')
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
event.locals.user = null;
|
event.locals.user = null
|
||||||
event.locals.session = null;
|
event.locals.session = null
|
||||||
return resolve(event);
|
return resolve(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await getUserAndSession(sessionId);
|
const res = await getUserAndSession(sessionId)
|
||||||
if(res.isNone()) {
|
if (res.isNone()) {
|
||||||
const sessionCookie = cookieController.createBlankCookie();
|
const sessionCookie = cookieController.createBlankCookie()
|
||||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: ".",
|
path: '.',
|
||||||
...sessionCookie.attributes
|
...sessionCookie.attributes
|
||||||
});
|
})
|
||||||
event.locals.user = null;
|
event.locals.user = null
|
||||||
event.locals.session = null;
|
event.locals.session = null
|
||||||
return resolve(event);
|
return resolve(event)
|
||||||
}
|
}
|
||||||
const { session, user } = res.unwrap()
|
const { session, user } = res.unwrap()
|
||||||
const sessionCookie = cookieController.createCookie(session.id)
|
const sessionCookie = cookieController.createCookie(session.id)
|
||||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: ".",
|
path: '.',
|
||||||
...sessionCookie.attributes
|
...sessionCookie.attributes
|
||||||
})
|
})
|
||||||
event.locals.user = user;
|
event.locals.user = user
|
||||||
event.locals.session = session;
|
event.locals.session = session
|
||||||
return resolve(event);
|
return resolve(event)
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
let { tab = $bindable(), disabled = $bindable(false), map, ...props }: {
|
let {
|
||||||
tab: string | undefined,
|
tab = $bindable(),
|
||||||
disabled?: boolean,
|
disabled = $bindable(false),
|
||||||
map?: Record<string, string>,
|
map,
|
||||||
class?: string,
|
...props
|
||||||
[x: `_${string}`]: Snippet<[]>;
|
}: {
|
||||||
} = $props();
|
tab: string | undefined
|
||||||
const tabs = Object.fromEntries(Object.entries(props).filter((v) => v[0].startsWith('_')).map(([name, snippet]): [string, Snippet<[]>] => [name.replace('_', ''), snippet]));
|
disabled?: boolean
|
||||||
|
map?: Record<string, string>
|
||||||
|
class?: string
|
||||||
|
[x: `_${string}`]: Snippet<[]>
|
||||||
|
} = $props()
|
||||||
|
const tabs = Object.fromEntries(
|
||||||
|
Object.entries(props)
|
||||||
|
.filter((v) => v[0].startsWith('_'))
|
||||||
|
.map(([name, snippet]): [string, Snippet<[]>] => [name.replace('_', ''), snippet])
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div role="tablist" class="tabs tabs-boxed {props.class}">
|
<div role="tablist" class="tabs tabs-boxed {props.class}">
|
||||||
|
@ -16,6 +25,14 @@
|
||||||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_missing_attribute -->
|
<!-- svelte-ignore a11y_missing_attribute -->
|
||||||
<a class:cursor-not-allowed={disabled} class:pointer-events-none={disabled} class:tab-disabled={disabled} role="tab" class="tab uppercase" onclick={() => tab = tabID} class:tab-active={tabID == tab}>{@render tabs[tabID]()}</a>
|
<a
|
||||||
|
class:cursor-not-allowed={disabled}
|
||||||
|
class:pointer-events-none={disabled}
|
||||||
|
class:tab-disabled={disabled}
|
||||||
|
role="tab"
|
||||||
|
class="tab uppercase"
|
||||||
|
onclick={() => (tab = tabID)}
|
||||||
|
class:tab-active={tabID == tab}>{@render tabs[tabID]()}</a
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
|
@ -1,10 +1,10 @@
|
||||||
import type { api as API } from "./server/hono"
|
import type { api as API } from './server/hono'
|
||||||
import { hc } from "hono/client"
|
import { hc } from 'hono/client'
|
||||||
export const api = hc<API>('/api', {
|
export const api = hc<API>('/api', {
|
||||||
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||||
return await fetch(input, {
|
return await fetch(input, {
|
||||||
...init,
|
...init,
|
||||||
credentials: 'include',
|
credentials: 'include'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,12 +1,10 @@
|
||||||
import type { z } from 'zod'
|
import type { z } from 'zod'
|
||||||
import { db, session } from './db'
|
import { db, session } from './db'
|
||||||
import { Err, Ok, Result } from '@oxi/result'
|
|
||||||
import { Option, None, Some } from '@oxi/option'
|
import { Option, None, Some } from '@oxi/option'
|
||||||
import type { FlatDocumentData } from '@olli/kvdex'
|
import type { FlatDocumentData } from '@olli/kvdex'
|
||||||
import { nanoid } from 'nanoid'
|
|
||||||
import { TimeSpan, createDate } from 'oslo'
|
import { TimeSpan, createDate } from 'oslo'
|
||||||
import { alphabet, generateRandomString } from 'oslo/crypto'
|
import { alphabet, generateRandomString } from 'oslo/crypto'
|
||||||
import { CookieController } from "oslo/cookie"
|
import { CookieController } from 'oslo/cookie'
|
||||||
|
|
||||||
const sessionTimeSpan = new TimeSpan(1, 'w')
|
const sessionTimeSpan = new TimeSpan(1, 'w')
|
||||||
|
|
||||||
|
@ -32,9 +30,7 @@ async function deleteSession(sessionId: string): Promise<void> {
|
||||||
await db.session.delete(sessionId)
|
await db.session.delete(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUserAndSession(
|
async function getUserAndSession(sessionId: string): Promise<
|
||||||
sessionId: string
|
|
||||||
): Promise<
|
|
||||||
Option<{
|
Option<{
|
||||||
user: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['user']>, string>
|
user: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['user']>, string>
|
||||||
session: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['session']>, string>
|
session: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['session']>, string>
|
||||||
|
@ -44,17 +40,25 @@ async function getUserAndSession(
|
||||||
if (!session) return None
|
if (!session) return None
|
||||||
const user = (await db.user.find(session.userId))?.flat()
|
const user = (await db.user.find(session.userId))?.flat()
|
||||||
if (!user) return None
|
if (!user) return None
|
||||||
await db.session.update(sessionId, {
|
await db.session.update(
|
||||||
expiresAt: createDate(sessionTimeSpan)
|
sessionId,
|
||||||
}, { expireIn: sessionTimeSpan.milliseconds() })
|
{
|
||||||
return Some({ user, session })
|
expiresAt: createDate(sessionTimeSpan)
|
||||||
|
},
|
||||||
|
{ expireIn: sessionTimeSpan.milliseconds() }
|
||||||
|
)
|
||||||
|
return Some({ user, session })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cookieExpiration = new TimeSpan(365 * 2, 'd')
|
export const cookieExpiration = new TimeSpan(365 * 2, 'd')
|
||||||
export const cookieController = new CookieController('auth_session', {
|
export const cookieController = new CookieController(
|
||||||
httpOnly: true,
|
'auth_session',
|
||||||
secure: true,
|
{
|
||||||
sameSite: "lax",
|
httpOnly: true,
|
||||||
path: "/",
|
secure: true,
|
||||||
}, { expiresIn: cookieExpiration })
|
sameSite: 'lax',
|
||||||
|
path: '/'
|
||||||
|
},
|
||||||
|
{ expiresIn: cookieExpiration }
|
||||||
|
)
|
||||||
export { createSessionForUser, deleteSession, getUserAndSession }
|
export { createSessionForUser, deleteSession, getUserAndSession }
|
||||||
|
|
|
@ -1,50 +1,51 @@
|
||||||
import { kvdex as kvdex, collection, model } from "@olli/kvdex"
|
import { kvdex as kvdex, collection, model } from '@olli/kvdex'
|
||||||
import { openKv } from "@deno/kv"
|
import { openKv } from '@deno/kv'
|
||||||
import { z } from "zod"
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const user = z.object({
|
export const user = z.object({
|
||||||
displayName: z.string(),
|
displayName: z.string(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
password: z.optional(z.string()),
|
password: z.optional(z.string()),
|
||||||
oauth_github_id: z.optional(z.string()),
|
oauth_github_id: z.optional(z.string()),
|
||||||
oauth_google_id: z.optional(z.string()),
|
oauth_google_id: z.optional(z.string()),
|
||||||
oauth_discord_id: z.optional(z.string())
|
oauth_discord_id: z.optional(z.string())
|
||||||
})
|
})
|
||||||
|
|
||||||
export const publicUser = user.pick({ displayName: true, id: true })
|
export const publicUser = user.pick({ displayName: true, id: true })
|
||||||
|
|
||||||
export const session = z.object({
|
export const session = z.object({
|
||||||
expiresAt: z.date(),
|
expiresAt: z.date(),
|
||||||
userId: z.string(),
|
userId: z.string()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const chat = z.object({
|
export const chat = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
creator: z.string().describe('id'),
|
creator: z.string().describe('id'),
|
||||||
createdAt: z.date()
|
createdAt: z.date()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const kv = await openKv('http://0.0.0.0:4512')
|
export const kv = await openKv('http://0.0.0.0:4512')
|
||||||
export const db = kvdex(kv, {
|
export const db = kvdex(kv, {
|
||||||
user: collection(user, {
|
user: collection(user, {
|
||||||
idGenerator: ({ id }) => id,
|
idGenerator: ({ id }) => id,
|
||||||
indices: {
|
indices: {
|
||||||
username: 'primary',
|
username: 'primary',
|
||||||
oauth_github_id: 'primary',
|
oauth_github_id: 'primary',
|
||||||
oauth_google_id: 'primary',
|
oauth_google_id: 'primary',
|
||||||
oauth_discord_id: 'primary'
|
oauth_discord_id: 'primary'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
session: collection(session, {
|
session: collection(session, {
|
||||||
indices: {
|
indices: {
|
||||||
userId: 'secondary'
|
userId: 'secondary'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
chat: {
|
chat: {
|
||||||
boxes: collection(model<{ roomID: string, userID: string, text: string }>()),
|
boxes: collection(model<{ roomID: string; userID: string; text: string }>()),
|
||||||
users: collection(publicUser),
|
users: collection(publicUser),
|
||||||
updatekey: collection(model<true>()),
|
updatekey: collection(model<true>()),
|
||||||
data: collection(chat)
|
data: collection(chat)
|
||||||
}
|
},
|
||||||
|
saved_oauth_data: collection(model<{ type: 'create' | 'link'; oauth_id: string }>())
|
||||||
})
|
})
|
|
@ -40,18 +40,18 @@ const api = new Hono<{ Bindings: Bindings }>()
|
||||||
return text(roomId)
|
return text(roomId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.get('/rooms/connect/:id', (c) => {
|
.get('/rooms/connect/:id', (c) => {
|
||||||
return streamSSE(c, async (stream) => {
|
return streamSSE(c, async (stream) => {
|
||||||
while (true) {
|
while (true) {
|
||||||
const message = `It is ${new Date().toISOString()}`
|
const message = `It is ${new Date().toISOString()}`
|
||||||
await stream.writeSSE({
|
await stream.writeSSE({
|
||||||
data: message,
|
data: message,
|
||||||
event: 'time-update',
|
event: 'time-update'
|
||||||
})
|
})
|
||||||
await stream.sleep(1000)
|
await stream.sleep(1000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export type api = typeof api
|
export type api = typeof api
|
||||||
|
|
||||||
|
|
|
@ -1,131 +1,211 @@
|
||||||
import { Google, Discord, GitHub, type OAuth2Provider, type OAuth2ProviderWithPKCE, generateState, generateCodeVerifier } from "arctic"
|
import { Google, Discord, GitHub, generateState, generateCodeVerifier } from 'arctic'
|
||||||
import { error, redirect, type Actions, type RequestHandler, type ServerLoad } from "@sveltejs/kit";
|
import { error, redirect, type Actions, type RequestHandler, type ServerLoad } from '@sveltejs/kit'
|
||||||
import type { z } from "zod";
|
import { z } from 'zod'
|
||||||
import type { publicUser } from "./db";
|
import { db, publicUser } from './db'
|
||||||
|
import { env } from '$env/dynamic/private'
|
||||||
|
import { dev } from '$app/environment'
|
||||||
|
import { alphabet, generateRandomString } from 'oslo/crypto'
|
||||||
|
import { decodeJwt } from 'jose'
|
||||||
|
import { superForm, type SuperForm } from 'sveltekit-superforms'
|
||||||
|
import { zod, type ValidationAdapter } from 'sveltekit-superforms/adapters'
|
||||||
|
|
||||||
|
const {
|
||||||
|
DISCORD_CLIENT_ID,
|
||||||
|
DISCORD_CLIENT_SECRET,
|
||||||
|
GITHUB_CLIENT_ID,
|
||||||
|
GITHUB_CLIENT_SECRET,
|
||||||
|
GOOGLE_CLIENT_ID,
|
||||||
|
GOOGLE_CLIENT_SECRET
|
||||||
|
} = env
|
||||||
|
|
||||||
//TODO: oauth
|
//TODO: oauth
|
||||||
|
|
||||||
export const google = new Google();
|
const DEVURL = (prov: string) => `http://localhost:5173/oauth/${prov}/callback`
|
||||||
export const discord = new Discord();
|
const PRODURL = (prov: string) => `https://spiel.place/oauth/${prov}/callback`
|
||||||
export const github = new GitHub();
|
|
||||||
|
export const github = new GitHub(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, {
|
||||||
|
redirectURI: dev ? DEVURL('github') : PRODURL('github')
|
||||||
|
})
|
||||||
|
export const discord = new Discord(
|
||||||
|
DISCORD_CLIENT_ID,
|
||||||
|
DISCORD_CLIENT_SECRET,
|
||||||
|
dev ? DEVURL('discord') : PRODURL('discord')
|
||||||
|
)
|
||||||
|
export const google = new Google(
|
||||||
|
GOOGLE_CLIENT_ID,
|
||||||
|
GOOGLE_CLIENT_SECRET,
|
||||||
|
dev ? DEVURL('google') : PRODURL('google')
|
||||||
|
)
|
||||||
|
|
||||||
export function oauth_handler(): RequestHandler<{ provider: string }> {
|
export function oauth_handler(): RequestHandler<{ provider: string }> {
|
||||||
return async ({ cookies, params: { provider: providerID }, url }) => {
|
return async ({ cookies, params: { provider: providerID }, url }) => {
|
||||||
let provider: Google | Discord | GitHub;
|
let provider: Google | Discord | GitHub
|
||||||
let scopes: string[]
|
let scopes: string[]
|
||||||
let noop = false;
|
let noop = false
|
||||||
switch(providerID) {
|
switch (providerID) {
|
||||||
case "discord": {
|
case 'discord': {
|
||||||
provider = discord
|
provider = discord
|
||||||
scopes = ['identify']
|
scopes = ['identify']
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "google": {
|
case 'google': {
|
||||||
provider = google
|
provider = google
|
||||||
scopes = ['profile']
|
scopes = ['profile']
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "github": {
|
case 'github': {
|
||||||
provider = github
|
provider = github
|
||||||
scopes = []
|
scopes = []
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
noop = true
|
noop = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @ts-expect-error: I know im using it before assignment that's the point
|
// @ts-expect-error: I know im using it before assignment that's the point
|
||||||
if(noop || !provider || !scopes) error(404, "provider not found")
|
if (noop || !provider || !scopes) error(404, 'provider not found')
|
||||||
let redir: URL;
|
let redir: URL
|
||||||
const state = generateState();
|
const state = generateState()
|
||||||
let codeVerifier: string;
|
let codeVerifier: string
|
||||||
if(provider instanceof Google) {
|
if (provider instanceof Google) {
|
||||||
codeVerifier = generateCodeVerifier()
|
codeVerifier = generateCodeVerifier()
|
||||||
redir = await provider.createAuthorizationURL(state, codeVerifier, {
|
redir = await provider.createAuthorizationURL(state, codeVerifier, {
|
||||||
scopes
|
scopes
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
redir = await provider.createAuthorizationURL(state, {
|
redir = await provider.createAuthorizationURL(state, {
|
||||||
scopes
|
scopes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
cookies.set("state", state, {
|
cookies.set('state', state, {
|
||||||
secure: true,
|
secure: true,
|
||||||
path: "/",
|
path: '/',
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: 60 * 10
|
maxAge: 60 * 10
|
||||||
});
|
})
|
||||||
// @ts-expect-error: I know im using it before assignment that's the point
|
// @ts-expect-error: I know im using it before assignment that's the point
|
||||||
if(codeVerifier) {
|
if (codeVerifier) {
|
||||||
cookies.set("code_verifier", codeVerifier, {
|
cookies.set('code_verifier', codeVerifier, {
|
||||||
secure: true,
|
secure: true,
|
||||||
path: "/",
|
path: '/',
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: 60 * 10
|
maxAge: 60 * 10
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
redirect(302, redir);
|
redirect(302, redir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oauth_callback(): ServerLoad<{ provider: string }, any, { type: "create" | "link", name: string, user: z.infer<typeof publicUser> }> {
|
export function oauth_callback(): ServerLoad<
|
||||||
return async ({ cookies, params: { provider: providerID }, locals, url }) => {
|
{ provider: string },
|
||||||
let provider: Google | Discord | GitHub;
|
any,
|
||||||
let scopes: string[]
|
{
|
||||||
let noop = false;
|
type: 'create' | 'link'
|
||||||
switch(providerID) {
|
name: string
|
||||||
case "discord": {
|
user: z.infer<typeof publicUser>
|
||||||
provider = discord
|
form: SuperForm<
|
||||||
scopes = ['identify']
|
ValidationAdapter<
|
||||||
break
|
{
|
||||||
}
|
token: string
|
||||||
case "google": {
|
},
|
||||||
provider = google
|
{
|
||||||
scopes = ['profile']
|
token: string
|
||||||
break
|
}
|
||||||
}
|
>,
|
||||||
case "github": {
|
any
|
||||||
provider = github
|
>
|
||||||
scopes = []
|
prov: string
|
||||||
break
|
}
|
||||||
}
|
> {
|
||||||
default: {
|
return async ({ cookies, params: { provider: providerID }, locals, url }) => {
|
||||||
noop = true
|
let provider: Google | Discord | GitHub
|
||||||
break
|
let scopes: string[]
|
||||||
}
|
let noop = false
|
||||||
}
|
switch (providerID) {
|
||||||
// @ts-expect-error: I know im using it before assignment that's the point
|
case 'discord': {
|
||||||
if(noop || !provider || !scopes) error(404, "provider not found")
|
provider = discord
|
||||||
const code = url.searchParams.get("code");
|
scopes = ['identify']
|
||||||
const state = url.searchParams.get("state");
|
break
|
||||||
|
}
|
||||||
|
case 'google': {
|
||||||
|
provider = google
|
||||||
|
scopes = ['profile']
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'github': {
|
||||||
|
provider = github
|
||||||
|
scopes = []
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
noop = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-expect-error: I know im using it before assignment that's the point
|
||||||
|
if (noop || !provider || !scopes) error(404, 'provider not found')
|
||||||
|
const code = url.searchParams.get('code')
|
||||||
|
const state = url.searchParams.get('state')
|
||||||
|
|
||||||
const storedState = cookies.get("state");
|
const storedState = cookies.get('state')
|
||||||
const storedCodeVerifier = cookies.get("code_verifier");
|
const storedCodeVerifier = cookies.get('code_verifier')
|
||||||
if (!code || !storedState || state !== storedState || (provider instanceof Google && !storedCodeVerifier)) {
|
if (
|
||||||
error(400, "Invalid request")
|
!code ||
|
||||||
}
|
!storedState ||
|
||||||
let tokens
|
state !== storedState ||
|
||||||
if(provider instanceof Google) {
|
(provider instanceof Google && !storedCodeVerifier)
|
||||||
tokens = await provider.validateAuthorizationCode(code, storedCodeVerifier)
|
) {
|
||||||
} else {
|
error(400, 'Invalid request')
|
||||||
tokens = await provider.validateAuthorizationCode(code)
|
}
|
||||||
}
|
let tokens
|
||||||
if(locals.user) {
|
let id
|
||||||
// the user is already logged in, ask them if they want to link the account to their existing account, or log out and try again
|
let name
|
||||||
return {
|
if (provider instanceof Google) {
|
||||||
type: 'create',
|
tokens = await provider.validateAuthorizationCode(code, storedCodeVerifier)
|
||||||
|
console.log(tokens.idToken)
|
||||||
}
|
const { sub, name: Uname } = decodeJwt(tokens.idToken)
|
||||||
} else {
|
id = sub
|
||||||
// the user is NOT logged in, log them in, if there is no account linked to that provided user, ask them if they want to create an account
|
name = Uname
|
||||||
|
} else {
|
||||||
}
|
tokens = await provider.validateAuthorizationCode(code)
|
||||||
}
|
}
|
||||||
|
const formToken = generateRandomString(12, alphabet('0-9', 'a-z'))
|
||||||
|
const form = superForm(
|
||||||
|
zod(z.object({ token: z.string() }), {
|
||||||
|
defaults: { token: formToken }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (locals.user) {
|
||||||
|
// the user is already logged in, ask them if they want to link the account to their existing account, or log out and try again
|
||||||
|
await db.saved_oauth_data.set(formToken, {
|
||||||
|
oauth_id: id,
|
||||||
|
type: 'link'
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
type: 'link',
|
||||||
|
name,
|
||||||
|
prov: providerID,
|
||||||
|
user: publicUser.safeParse(locals.user).data!,
|
||||||
|
form
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the user is NOT logged in, log them in, if there is no account linked to that provided user, ask them if they want to create an account
|
||||||
|
await db.saved_oauth_data.set(formToken, {
|
||||||
|
oauth_id: id,
|
||||||
|
type: 'create'
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
type: 'create',
|
||||||
|
name,
|
||||||
|
prov: providerID,
|
||||||
|
user: publicUser.safeParse(locals.user).data!,
|
||||||
|
form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function callback_actions(): Actions<{ provider: string }> {
|
export function oauth_callback_actions(): Actions<{ provider: string }> {
|
||||||
return {
|
return {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import "../app.pcss"
|
import '../app.pcss'
|
||||||
const { children } = $props()
|
const { children } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
|
@ -1,5 +1,5 @@
|
||||||
import { redirect } from "@sveltejs/kit";
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
return redirect(302, '/app')
|
return redirect(302, '/app')
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { hono } from '$lib/server/hono.js';
|
import { hono } from '$lib/server/hono'
|
||||||
|
|
||||||
export async function fallback({ request, locals }) {
|
export async function fallback({ request, locals }) {
|
||||||
return hono.fetch(request, { locals })
|
return hono.fetch(request, { locals })
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import { publicUser } from '$lib/server/db.js';
|
import { publicUser } from '$lib/server/db'
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
export async function load({ locals }) {
|
export async function load({ locals }) {
|
||||||
if(!locals.session) {
|
if (!locals.session) {
|
||||||
return redirect(302, "/auth")
|
return redirect(302, '/auth')
|
||||||
}
|
}
|
||||||
return publicUser.safeParse(locals.user).data!
|
return publicUser.safeParse(locals.user).data!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const csr = true;
|
export const csr = true
|
||||||
export const ssr = false;
|
export const ssr = false
|
||||||
|
|
|
@ -5,73 +5,92 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-[100vh] w-[100vw] flex flex-col">
|
<div class="h-[100vh] w-[100vw] flex flex-col">
|
||||||
<div class="px-4">
|
<div class="px-4">
|
||||||
<div class="navbar bg-base-200 rounded-b-xl">
|
<div class="navbar bg-base-200 rounded-b-xl">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<button class="btn btn-ghost text-xl">spiel.place</button>
|
<button class="btn btn-ghost text-xl">spiel.place</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<details
|
<details
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end"
|
||||||
ontoggle={({ newState }) => (dropdownOpen = newState === 'open')}>
|
ontoggle={({ newState }) => (dropdownOpen = newState === 'open')}
|
||||||
<summary class="btn btn-circle btn-ghost m-1 swap swap-rotate">
|
>
|
||||||
<input type="checkbox" bind:checked={dropdownOpen} />
|
<summary class="btn btn-circle btn-ghost m-1 swap swap-rotate">
|
||||||
<svg
|
<input type="checkbox" bind:checked={dropdownOpen} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
viewBox="0 0 20 20"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="currentColor"
|
viewBox="0 0 20 20"
|
||||||
class="size-5 swap-off">
|
fill="currentColor"
|
||||||
<path
|
class="size-5 swap-off"
|
||||||
fill-rule="evenodd"
|
>
|
||||||
d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z"
|
<path
|
||||||
clip-rule="evenodd"></path>
|
fill-rule="evenodd"
|
||||||
</svg>
|
d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z"
|
||||||
<svg
|
clip-rule="evenodd"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
></path>
|
||||||
fill="none"
|
</svg>
|
||||||
viewBox="0 0 24 24"
|
<svg
|
||||||
stroke-width="1.5"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke="currentColor"
|
fill="none"
|
||||||
class="size-5 swap-on stroke-2">
|
viewBox="0 0 24 24"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
stroke-width="1.5"
|
||||||
</svg>
|
stroke="currentColor"
|
||||||
</summary>
|
class="size-5 swap-on stroke-2"
|
||||||
<ul class="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
|
>
|
||||||
<li><a href="/app/settings">
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
</svg>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
</summary>
|
||||||
</svg>
|
<ul class="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
|
||||||
{data.displayName}
|
<li>
|
||||||
</a></li>
|
<a href="/app/settings">
|
||||||
<li>
|
<svg
|
||||||
<a href="/auth/signout">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
stroke-width="1.5"
|
||||||
viewBox="0 0 24 24"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
class="size-6"
|
||||||
stroke="currentColor"
|
>
|
||||||
class="size-6">
|
<path
|
||||||
<path
|
stroke-linecap="round"
|
||||||
stroke-linecap="round"
|
stroke-linejoin="round"
|
||||||
stroke-linejoin="round"
|
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"
|
></path>
|
||||||
></path>
|
</svg>
|
||||||
</svg>
|
{data.displayName}
|
||||||
sign out</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
</details>
|
<a href="/auth/signout">
|
||||||
</div>
|
<svg
|
||||||
</div>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</div>
|
fill="none"
|
||||||
<div class="flex-grow flex flex-row">
|
viewBox="0 0 24 24"
|
||||||
{@render children()}
|
stroke-width="1.5"
|
||||||
</div>
|
stroke="currentColor"
|
||||||
|
class="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
sign out</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow flex flex-row">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toast">
|
<div class="toast">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<span>New message arrived.</span>
|
<span>New message arrived.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
<script>
|
<script>
|
||||||
import { api } from '$lib/apiclient'
|
import { api } from '$lib/apiclient'
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from '$app/navigation'
|
||||||
|
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
let roomname = $state('')
|
let roomname = $state('')
|
||||||
|
|
||||||
async function createRoom() {
|
async function createRoom() {
|
||||||
const res = await api.rooms.create.$post({
|
const res = await api.rooms.create.$post({
|
||||||
json: {
|
json: {
|
||||||
name: roomname
|
name: roomname
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(!res.ok) return
|
if (!res.ok) return
|
||||||
const id = await res.text()
|
const id = await res.text()
|
||||||
goto(`/app/:${id}`)
|
goto(`/app/:${id}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-56 flex flex-col justify-center">
|
<div class="w-56 flex flex-col justify-center">
|
||||||
<div class="my-10 px-2 py-2 rounded-r-lg bg-base-200">
|
<div class="my-10 px-2 py-2 rounded-r-lg bg-base-200">
|
||||||
<span class="text-md font-bold mb-2">new room</span>
|
<span class="text-md font-bold mb-2">new room</span>
|
||||||
<input bind:value={roomname} minlength=4 class="input input-bordered w-full placeholder:text-base-content/50 mb-2" type="text" placeholder="name">
|
<input
|
||||||
<button on:click={createRoom} class="btn btn-secondary w-full">create</button>
|
bind:value={roomname}
|
||||||
</div>
|
minlength="4"
|
||||||
|
class="input input-bordered w-full placeholder:text-base-content/50 mb-2"
|
||||||
|
type="text"
|
||||||
|
placeholder="name"
|
||||||
|
/>
|
||||||
|
<button on:click={createRoom} class="btn btn-secondary w-full">create</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<main class="flex-grow">
|
<main class="flex-grow">list</main>
|
||||||
list
|
|
||||||
</main>
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{data.roomID}
|
{data.roomID}
|
|
@ -1,3 +1,3 @@
|
||||||
export async function load({ params: { roomID } }) {
|
export async function load({ params: { roomID } }) {
|
||||||
return { roomID }
|
return { roomID }
|
||||||
}
|
}
|
|
@ -1,75 +1,80 @@
|
||||||
import { alphabet, generateRandomString } from 'oslo/crypto';
|
import { alphabet, generateRandomString } from 'oslo/crypto'
|
||||||
import { fail, message, setError, superValidate } from 'sveltekit-superforms';
|
import { fail, message, setError, superValidate } from 'sveltekit-superforms'
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
import { zod } from 'sveltekit-superforms/adapters'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod'
|
||||||
import { hash, verify } from "@ts-rex/argon2"
|
import { hash, verify } from '@ts-rex/argon2'
|
||||||
import { db } from '$lib/server/db.js';
|
import { db } from '$lib/server/db'
|
||||||
import { cookieController, cookieExpiration, createSessionForUser } from '$lib/server/auth.js';
|
import { cookieController, cookieExpiration, createSessionForUser } from '$lib/server/auth'
|
||||||
import { createDate } from 'oslo';
|
import { createDate } from 'oslo'
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
username: z.string().min(4, "must be atleast 4 characters").max(32, "must be less than 32 characters").regex(/^[a-z0-9_\-]+$/i, `must be alphanumeric, with the exception of "_" and "-"`),
|
username: z
|
||||||
password: z.string().min(8, "must be atleast 8 characters").max(255)
|
.string()
|
||||||
});
|
.min(4, 'must be atleast 4 characters')
|
||||||
|
.max(32, 'must be less than 32 characters')
|
||||||
|
.regex(/^[a-z0-9_\-]+$/i, `must be alphanumeric, with the exception of "_" and "-"`),
|
||||||
|
password: z.string().min(8, 'must be atleast 8 characters').max(255)
|
||||||
|
})
|
||||||
|
|
||||||
export async function load({ locals }) {
|
export async function load({ locals }) {
|
||||||
if(locals.session) {
|
if (locals.session) {
|
||||||
return redirect(302, '/app')
|
return redirect(302, '/app')
|
||||||
}
|
}
|
||||||
const form = await superValidate(zod(schema));
|
const form = await superValidate(zod(schema))
|
||||||
return { form };
|
return { form }
|
||||||
};
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
login: async ({ request, cookies }) => {
|
login: async ({ request, cookies }) => {
|
||||||
const form = await superValidate(request, zod(schema));
|
const form = await superValidate(request, zod(schema))
|
||||||
|
|
||||||
if (!form.valid) return fail(400, { form });
|
if (!form.valid) return fail(400, { form })
|
||||||
const { username, password } = form.data
|
const { username, password } = form.data
|
||||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat();
|
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
||||||
if (!user) return setError(form, "user does not exist")
|
if (!user) return setError(form, 'user does not exist')
|
||||||
if (!user.password) return setError(form, "this account does not have a password, maybe try a different method?")
|
if (!user.password)
|
||||||
const isvalid = verify(password, user.password);
|
return setError(form, 'this account does not have a password, maybe try a different method?')
|
||||||
if (!isvalid) return setError(form, "incorrect password")
|
const isvalid = verify(password, user.password)
|
||||||
const session = (await createSessionForUser(user.id))
|
if (!isvalid) return setError(form, 'incorrect password')
|
||||||
if (session.isSome()) {
|
const session = await createSessionForUser(user.id)
|
||||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
if (session.isSome()) {
|
||||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||||
path: ".",
|
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
...sessionCookie.attributes
|
path: '.',
|
||||||
})
|
...sessionCookie.attributes
|
||||||
return redirect(302, '/app')
|
})
|
||||||
} else {
|
return redirect(302, '/app')
|
||||||
return fail(500, { form })
|
} else {
|
||||||
}
|
return fail(500, { form })
|
||||||
},
|
}
|
||||||
signup: async ({ request, cookies }) => {
|
},
|
||||||
const form = await superValidate(request, zod(schema));
|
signup: async ({ request, cookies }) => {
|
||||||
if (!form.valid) return fail(400, { form });
|
const form = await superValidate(request, zod(schema))
|
||||||
const { username, password } = form.data
|
if (!form.valid) return fail(400, { form })
|
||||||
|
const { username, password } = form.data
|
||||||
|
|
||||||
const userId = generateRandomString(10, alphabet("0-9", "a-z"))
|
const userId = generateRandomString(10, alphabet('0-9', 'a-z'))
|
||||||
const passwordHash = hash(password)
|
const passwordHash = hash(password)
|
||||||
|
|
||||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
||||||
if (user) return setError(form, "username", 'username already exists')
|
if (user) return setError(form, 'username', 'username already exists')
|
||||||
await db.user.set(userId, {
|
await db.user.set(userId, {
|
||||||
displayName: username,
|
displayName: username,
|
||||||
username,
|
username,
|
||||||
id: userId,
|
id: userId,
|
||||||
password: passwordHash
|
password: passwordHash
|
||||||
})
|
})
|
||||||
const session = (await createSessionForUser(userId))
|
const session = await createSessionForUser(userId)
|
||||||
if (session.isSome()) {
|
if (session.isSome()) {
|
||||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: ".",
|
path: '.',
|
||||||
...sessionCookie.attributes
|
...sessionCookie.attributes
|
||||||
})
|
})
|
||||||
return redirect(302, '/app')
|
return redirect(302, '/app')
|
||||||
} else {
|
} else {
|
||||||
return fail(500, { form })
|
return fail(500, { form })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,9 +18,9 @@
|
||||||
<div class="card bg-base-200 w-96 shadow-xl p-4">
|
<div class="card bg-base-200 w-96 shadow-xl p-4">
|
||||||
<h1 class="text-xl mb-2 font-bold">sign up/in</h1>
|
<h1 class="text-xl mb-2 font-bold">sign up/in</h1>
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<a href="{$page.url.pathname}/oauth/google" class="btn-disabled btn grow btn-outline join-item">
|
<a href="/oauth/google" class="btn grow btn-outline join-item">
|
||||||
<svg
|
<svg
|
||||||
class="size-4 brightness-50"
|
class="size-4"
|
||||||
viewBox="-3 0 262 262"
|
viewBox="-3 0 262 262"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
preserveAspectRatio="xMidYMid"
|
preserveAspectRatio="xMidYMid"
|
||||||
|
@ -28,32 +28,36 @@
|
||||||
><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g
|
><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g
|
||||||
id="SVGRepo_tracerCarrier"
|
id="SVGRepo_tracerCarrier"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"></g
|
stroke-linejoin="round"
|
||||||
><g id="SVGRepo_iconCarrier"
|
></g><g id="SVGRepo_iconCarrier"
|
||||||
><path
|
><path
|
||||||
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||||
fill="#4285F4"></path
|
fill="#4285F4"
|
||||||
><path
|
></path><path
|
||||||
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||||
fill="#34A853"></path
|
fill="#34A853"
|
||||||
><path
|
></path><path
|
||||||
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
|
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
|
||||||
fill="#FBBC05"></path
|
fill="#FBBC05"
|
||||||
><path
|
></path><path
|
||||||
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||||
fill="#EB4335"></path
|
fill="#EB4335"
|
||||||
></g
|
></path></g
|
||||||
></svg>
|
></svg
|
||||||
|
>
|
||||||
</a>
|
</a>
|
||||||
<a href="{$page.url.pathname}/oauth/discord" class="btn-disabled btn grow btn-outline join-item">
|
<a href="/oauth/discord" class="btn-disabled btn grow btn-outline join-item">
|
||||||
<svg class="size-4 brightness-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
|
<svg
|
||||||
|
class="size-4 brightness-50"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 127.14 96.36"
|
||||||
><path
|
><path
|
||||||
fill="#5865f2"
|
fill="#5865f2"
|
||||||
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
|
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
|
||||||
></path
|
></path></svg
|
||||||
></svg>
|
>
|
||||||
</a>
|
</a>
|
||||||
<a href="{$page.url.pathname}/oauth/github" class="btn-disabled btn grow btn-outline join-item group">
|
<a href="/oauth/github" class="btn-disabled btn grow btn-outline join-item group">
|
||||||
<svg
|
<svg
|
||||||
class="size-5 brightness-50 text-[#24292f] dark:text-[#fff] group-hover:text-[#fff] dark:group-hover:text-[#24292f] transition-colors"
|
class="size-5 brightness-50 text-[#24292f] dark:text-[#fff] group-hover:text-[#fff] dark:group-hover:text-[#24292f] transition-colors"
|
||||||
viewBox="0 0 98 96"
|
viewBox="0 0 98 96"
|
||||||
|
@ -62,24 +66,28 @@
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||||
fill="currentColor"></path
|
fill="currentColor"
|
||||||
></svg>
|
></path></svg
|
||||||
|
>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span class="divider">or</span>
|
<span class="divider">or</span>
|
||||||
<form class="flex-col flex gap-y-4" method="post" use:enhance>
|
<form class="flex-col flex gap-y-4" method="post" use:enhance>
|
||||||
<label
|
<label
|
||||||
class="input input-bordered flex items-center gap-2"
|
class="input input-bordered flex items-center gap-2"
|
||||||
class:input-error={!!$errors.username}>
|
class:input-error={!!$errors.username}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="size-4">
|
class="size-4"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"
|
d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"
|
||||||
clip-rule="evenodd"></path>
|
clip-rule="evenodd"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
@ -88,23 +96,28 @@
|
||||||
name="username"
|
name="username"
|
||||||
type="text"
|
type="text"
|
||||||
class="grow placeholder:text-base-content/20"
|
class="grow placeholder:text-base-content/20"
|
||||||
placeholder="kaii" />
|
placeholder="kaii"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
class="opacity-0 hidden transition-opacity duration-1000 text-error"
|
class="opacity-0 hidden transition-opacity duration-1000 text-error"
|
||||||
class:showerror={$errors.username}>{$errors.username?.join(' & ')}</span>
|
class:showerror={$errors.username}>{$errors.username?.join(' & ')}</span
|
||||||
|
>
|
||||||
<label
|
<label
|
||||||
class="input input-bordered flex items-center gap-2"
|
class="input input-bordered flex items-center gap-2"
|
||||||
class:input-error={!!$errors.password}>
|
class:input-error={!!$errors.password}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="size-4">
|
class="size-4"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
|
d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
|
||||||
clip-rule="evenodd"></path>
|
clip-rule="evenodd"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<input
|
<input
|
||||||
bind:value={password}
|
bind:value={password}
|
||||||
|
@ -112,11 +125,13 @@
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
class="grow placeholder:text-base-content/20"
|
class="grow placeholder:text-base-content/20"
|
||||||
placeholder="verygoodpassword" />
|
placeholder="verygoodpassword"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
class="opacity-0 hidden transition-opacity duration-1000 text-error"
|
class="opacity-0 hidden transition-opacity duration-1000 text-error"
|
||||||
class:showerror={$errors.password}>{$errors.password}</span>
|
class:showerror={$errors.password}>{$errors.password}</span
|
||||||
|
>
|
||||||
<div class="flex flex-row gap-x-4">
|
<div class="flex flex-row gap-x-4">
|
||||||
<button formaction="?/signup" type="submit" class="btn btn-secondary flex-grow">
|
<button formaction="?/signup" type="submit" class="btn btn-secondary flex-grow">
|
||||||
sign up
|
sign up
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { cookieController, deleteSession } from '$lib/server/auth';
|
import { cookieController, deleteSession } from '$lib/server/auth'
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
export async function GET({ locals, cookies }) {
|
export async function GET({ locals, cookies }) {
|
||||||
if(locals.session) {
|
if (locals.session) {
|
||||||
await deleteSession(locals.session.id)
|
await deleteSession(locals.session.id)
|
||||||
const sessionCookie = cookieController.createBlankCookie();
|
const sessionCookie = cookieController.createBlankCookie()
|
||||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: ".",
|
path: '.',
|
||||||
...sessionCookie.attributes
|
...sessionCookie.attributes
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return redirect(302, '/')
|
return redirect(302, '/')
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
import { oauth_handler } from "$lib/server/oauth";
|
import { oauth_handler } from '$lib/server/oauth'
|
||||||
|
|
||||||
export const GET = oauth_handler();
|
export const GET = oauth_handler()
|
||||||
|
|
4
src/routes/oauth/[provider]/callback/+page.server.ts
Normal file
4
src/routes/oauth/[provider]/callback/+page.server.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { oauth_callback, oauth_callback_actions } from '$lib/server/oauth'
|
||||||
|
|
||||||
|
export const load = oauth_callback()
|
||||||
|
export const actions = oauth_callback_actions()
|
8
src/routes/oauth/[provider]/callback/+page.svelte
Normal file
8
src/routes/oauth/[provider]/callback/+page.svelte
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
const { data } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if data.type === 'create'}
|
||||||
|
do you want to create a new account using <code class="font-bold">{data.name}</code> from {data.prov}?
|
||||||
|
<button>yes</button> <button>no</button>
|
||||||
|
{:else if data.type === 'link'}{/if}
|
|
@ -1,7 +1,7 @@
|
||||||
import adapter from 'sveltekit-adapter-deno';
|
import adapter from 'sveltekit-adapter-deno'
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
import { Float16Array } from "@petamoriken/float16"
|
import { Float16Array } from '@petamoriken/float16'
|
||||||
// kvdex uses float16array under the hood (doesn't exist in node), filling that in here so it works during dev
|
// kvdex uses float16array under the hood (doesn't exist in node), filling that in here so it works during dev
|
||||||
globalThis.Float16Array = Float16Array
|
globalThis.Float16Array = Float16Array
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ const config = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import daisyui from "daisyui"
|
import daisyui from 'daisyui'
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {}
|
||||||
},
|
},
|
||||||
plugins: [daisyui],
|
plugins: [daisyui]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite'
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
|
@ -11,4 +11,4 @@ export default defineConfig({
|
||||||
target: ['deno1.45.5']
|
target: ['deno1.45.5']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
Loading…
Reference in a new issue