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",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"@deno/kv": "^0.8.1",
|
||||
"@hono/zod-validator": "^0.2.2",
|
||||
"@olli/kvdex": "npm:@jsr/olli__kvdex",
|
||||
"@oxi/option": "npm:@jsr/oxi__option",
|
||||
"@oxi/result": "npm:@jsr/oxi__result",
|
||||
"@petamoriken/float16": "^3.8.7",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
|
||||
"arctic": "^1.9.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"daisyui": "^4.12.10",
|
||||
"hono": "^4.5.5",
|
||||
"nanoid": "^5.0.7",
|
||||
"oslo": "^1.2.1",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.2.5",
|
||||
"svelte": "^5.0.0-next.1",
|
||||
"svelte-check": "^3.6.0",
|
||||
"sveltekit-adapter-deno": "^0.12.1",
|
||||
"sveltekit-superforms": "^2.17.0",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check ."
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"svelte-preprocess"
|
||||
],
|
||||
"type": "module"
|
||||
"name": "talkomatic",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"@deno/kv": "^0.8.1",
|
||||
"@hono/zod-validator": "^0.2.2",
|
||||
"@olli/kvdex": "npm:@jsr/olli__kvdex",
|
||||
"@oxi/option": "npm:@jsr/oxi__option",
|
||||
"@oxi/result": "npm:@jsr/oxi__result",
|
||||
"@petamoriken/float16": "^3.8.7",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
|
||||
"arctic": "^1.9.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"daisyui": "^4.12.10",
|
||||
"hono": "^4.5.5",
|
||||
"jose": "^5.6.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"oslo": "^1.2.1",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.2.5",
|
||||
"svelte": "^5.0.0-next.1",
|
||||
"svelte-check": "^3.6.0",
|
||||
"sveltekit-adapter-deno": "^0.12.1",
|
||||
"sveltekit-superforms": "^2.17.0",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check ."
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"svelte-preprocess"
|
||||
],
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
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
|
||||
// for information about these interfaces
|
||||
|
||||
import type { FlatDocumentData } from "@olli/kvdex"
|
||||
import { user, session } from "$lib/server/db"
|
||||
import z from "zod"
|
||||
import type { FlatDocumentData } from '@olli/kvdex'
|
||||
import { user, session } from '$lib/server/db'
|
||||
import z from 'zod'
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
|
@ -12,10 +12,10 @@ declare global {
|
|||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
interface Locals {
|
||||
user: FlatDocumentData<z.infer<typeof user>, string> | null;
|
||||
session: FlatDocumentData<z.infer<typeof session>, string> | null;
|
||||
user: FlatDocumentData<z.infer<typeof user>, 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 type { Handle } from "@sveltejs/kit";
|
||||
import { createDate } from "oslo";
|
||||
import { cookieController, cookieExpiration, getUserAndSession } from '$lib/server/auth'
|
||||
import type { Handle } from '@sveltejs/kit'
|
||||
import { createDate } from 'oslo'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const sessionId = event.cookies.get("auth_session");
|
||||
const sessionId = event.cookies.get('auth_session')
|
||||
if (!sessionId) {
|
||||
event.locals.user = null;
|
||||
event.locals.session = null;
|
||||
return resolve(event);
|
||||
event.locals.user = null
|
||||
event.locals.session = null
|
||||
return resolve(event)
|
||||
}
|
||||
|
||||
const res = await getUserAndSession(sessionId);
|
||||
if(res.isNone()) {
|
||||
const sessionCookie = cookieController.createBlankCookie();
|
||||
const res = await getUserAndSession(sessionId)
|
||||
if (res.isNone()) {
|
||||
const sessionCookie = cookieController.createBlankCookie()
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
});
|
||||
event.locals.user = null;
|
||||
event.locals.session = null;
|
||||
return resolve(event);
|
||||
}
|
||||
const { session, user } = res.unwrap()
|
||||
const sessionCookie = cookieController.createCookie(session.id)
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
event.locals.user = user;
|
||||
event.locals.session = session;
|
||||
return resolve(event);
|
||||
};
|
||||
})
|
||||
event.locals.user = null
|
||||
event.locals.session = null
|
||||
return resolve(event)
|
||||
}
|
||||
const { session, user } = res.unwrap()
|
||||
const sessionCookie = cookieController.createCookie(session.id)
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
event.locals.user = user
|
||||
event.locals.session = session
|
||||
return resolve(event)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
let { tab = $bindable(), disabled = $bindable(false), map, ...props }: {
|
||||
tab: string | undefined,
|
||||
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]));
|
||||
let {
|
||||
tab = $bindable(),
|
||||
disabled = $bindable(false),
|
||||
map,
|
||||
...props
|
||||
}: {
|
||||
tab: string | undefined
|
||||
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>
|
||||
|
||||
<div role="tablist" class="tabs tabs-boxed {props.class}">
|
||||
|
@ -16,6 +25,14 @@
|
|||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- 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}
|
||||
</div>
|
|
@ -1,10 +1,10 @@
|
|||
import type { api as API } from "./server/hono"
|
||||
import { hc } from "hono/client"
|
||||
import type { api as API } from './server/hono'
|
||||
import { hc } from 'hono/client'
|
||||
export const api = hc<API>('/api', {
|
||||
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
return await fetch(input, {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
return await fetch(input, {
|
||||
...init,
|
||||
credentials: 'include'
|
||||
})
|
||||
}
|
||||
})
|
|
@ -1,12 +1,10 @@
|
|||
import type { z } from 'zod'
|
||||
import { db, session } from './db'
|
||||
import { Err, Ok, Result } from '@oxi/result'
|
||||
import { Option, None, Some } from '@oxi/option'
|
||||
import type { FlatDocumentData } from '@olli/kvdex'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { TimeSpan, createDate } from 'oslo'
|
||||
import { alphabet, generateRandomString } from 'oslo/crypto'
|
||||
import { CookieController } from "oslo/cookie"
|
||||
import { CookieController } from 'oslo/cookie'
|
||||
|
||||
const sessionTimeSpan = new TimeSpan(1, 'w')
|
||||
|
||||
|
@ -32,9 +30,7 @@ async function deleteSession(sessionId: string): Promise<void> {
|
|||
await db.session.delete(sessionId)
|
||||
}
|
||||
|
||||
async function getUserAndSession(
|
||||
sessionId: string
|
||||
): Promise<
|
||||
async function getUserAndSession(sessionId: string): Promise<
|
||||
Option<{
|
||||
user: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['user']>, string>
|
||||
session: FlatDocumentData<z.infer<(typeof import('$lib/server/db'))['session']>, string>
|
||||
|
@ -44,17 +40,25 @@ async function getUserAndSession(
|
|||
if (!session) return None
|
||||
const user = (await db.user.find(session.userId))?.flat()
|
||||
if (!user) return None
|
||||
await db.session.update(sessionId, {
|
||||
expiresAt: createDate(sessionTimeSpan)
|
||||
}, { expireIn: sessionTimeSpan.milliseconds() })
|
||||
return Some({ user, session })
|
||||
await db.session.update(
|
||||
sessionId,
|
||||
{
|
||||
expiresAt: createDate(sessionTimeSpan)
|
||||
},
|
||||
{ expireIn: sessionTimeSpan.milliseconds() }
|
||||
)
|
||||
return Some({ user, session })
|
||||
}
|
||||
|
||||
export const cookieExpiration = new TimeSpan(365 * 2, 'd')
|
||||
export const cookieController = new CookieController('auth_session', {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
}, { expiresIn: cookieExpiration })
|
||||
export const cookieController = new CookieController(
|
||||
'auth_session',
|
||||
{
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
},
|
||||
{ expiresIn: cookieExpiration }
|
||||
)
|
||||
export { createSessionForUser, deleteSession, getUserAndSession }
|
||||
|
|
|
@ -1,50 +1,51 @@
|
|||
import { kvdex as kvdex, collection, model } from "@olli/kvdex"
|
||||
import { openKv } from "@deno/kv"
|
||||
import { z } from "zod"
|
||||
import { kvdex as kvdex, collection, model } from '@olli/kvdex'
|
||||
import { openKv } from '@deno/kv'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const user = z.object({
|
||||
displayName: z.string(),
|
||||
username: z.string(),
|
||||
id: z.string(),
|
||||
password: z.optional(z.string()),
|
||||
oauth_github_id: z.optional(z.string()),
|
||||
oauth_google_id: z.optional(z.string()),
|
||||
oauth_discord_id: z.optional(z.string())
|
||||
displayName: z.string(),
|
||||
username: z.string(),
|
||||
id: z.string(),
|
||||
password: z.optional(z.string()),
|
||||
oauth_github_id: z.optional(z.string()),
|
||||
oauth_google_id: z.optional(z.string()),
|
||||
oauth_discord_id: z.optional(z.string())
|
||||
})
|
||||
|
||||
export const publicUser = user.pick({ displayName: true, id: true })
|
||||
|
||||
export const session = z.object({
|
||||
expiresAt: z.date(),
|
||||
userId: z.string(),
|
||||
expiresAt: z.date(),
|
||||
userId: z.string()
|
||||
})
|
||||
|
||||
export const chat = z.object({
|
||||
name: z.string(),
|
||||
creator: z.string().describe('id'),
|
||||
createdAt: z.date()
|
||||
name: z.string(),
|
||||
creator: z.string().describe('id'),
|
||||
createdAt: z.date()
|
||||
})
|
||||
|
||||
export const kv = await openKv('http://0.0.0.0:4512')
|
||||
export const db = kvdex(kv, {
|
||||
user: collection(user, {
|
||||
idGenerator: ({ id }) => id,
|
||||
indices: {
|
||||
username: 'primary',
|
||||
oauth_github_id: 'primary',
|
||||
oauth_google_id: 'primary',
|
||||
oauth_discord_id: 'primary'
|
||||
}
|
||||
}),
|
||||
session: collection(session, {
|
||||
indices: {
|
||||
userId: 'secondary'
|
||||
}
|
||||
}),
|
||||
chat: {
|
||||
boxes: collection(model<{ roomID: string, userID: string, text: string }>()),
|
||||
users: collection(publicUser),
|
||||
updatekey: collection(model<true>()),
|
||||
data: collection(chat)
|
||||
}
|
||||
user: collection(user, {
|
||||
idGenerator: ({ id }) => id,
|
||||
indices: {
|
||||
username: 'primary',
|
||||
oauth_github_id: 'primary',
|
||||
oauth_google_id: 'primary',
|
||||
oauth_discord_id: 'primary'
|
||||
}
|
||||
}),
|
||||
session: collection(session, {
|
||||
indices: {
|
||||
userId: 'secondary'
|
||||
}
|
||||
}),
|
||||
chat: {
|
||||
boxes: collection(model<{ roomID: string; userID: string; text: string }>()),
|
||||
users: collection(publicUser),
|
||||
updatekey: collection(model<true>()),
|
||||
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)
|
||||
}
|
||||
)
|
||||
.get('/rooms/connect/:id', (c) => {
|
||||
return streamSSE(c, async (stream) => {
|
||||
while (true) {
|
||||
const message = `It is ${new Date().toISOString()}`
|
||||
await stream.writeSSE({
|
||||
data: message,
|
||||
event: 'time-update',
|
||||
})
|
||||
await stream.sleep(1000)
|
||||
}
|
||||
})
|
||||
})
|
||||
.get('/rooms/connect/:id', (c) => {
|
||||
return streamSSE(c, async (stream) => {
|
||||
while (true) {
|
||||
const message = `It is ${new Date().toISOString()}`
|
||||
await stream.writeSSE({
|
||||
data: message,
|
||||
event: 'time-update'
|
||||
})
|
||||
await stream.sleep(1000)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export type api = typeof api
|
||||
|
||||
|
|
|
@ -1,131 +1,211 @@
|
|||
import { Google, Discord, GitHub, type OAuth2Provider, type OAuth2ProviderWithPKCE, generateState, generateCodeVerifier } from "arctic"
|
||||
import { error, redirect, type Actions, type RequestHandler, type ServerLoad } from "@sveltejs/kit";
|
||||
import type { z } from "zod";
|
||||
import type { publicUser } from "./db";
|
||||
import { Google, Discord, GitHub, generateState, generateCodeVerifier } from 'arctic'
|
||||
import { error, redirect, type Actions, type RequestHandler, type ServerLoad } from '@sveltejs/kit'
|
||||
import { z } from 'zod'
|
||||
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
|
||||
|
||||
export const google = new Google();
|
||||
export const discord = new Discord();
|
||||
export const github = new GitHub();
|
||||
const DEVURL = (prov: string) => `http://localhost:5173/oauth/${prov}/callback`
|
||||
const PRODURL = (prov: string) => `https://spiel.place/oauth/${prov}/callback`
|
||||
|
||||
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 }> {
|
||||
return async ({ cookies, params: { provider: providerID }, url }) => {
|
||||
let provider: Google | Discord | GitHub;
|
||||
let scopes: string[]
|
||||
let noop = false;
|
||||
switch(providerID) {
|
||||
case "discord": {
|
||||
provider = discord
|
||||
scopes = ['identify']
|
||||
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")
|
||||
let redir: URL;
|
||||
const state = generateState();
|
||||
let codeVerifier: string;
|
||||
if(provider instanceof Google) {
|
||||
codeVerifier = generateCodeVerifier()
|
||||
redir = await provider.createAuthorizationURL(state, codeVerifier, {
|
||||
scopes
|
||||
})
|
||||
} else {
|
||||
redir = await provider.createAuthorizationURL(state, {
|
||||
scopes
|
||||
})
|
||||
}
|
||||
cookies.set("state", state, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10
|
||||
});
|
||||
// @ts-expect-error: I know im using it before assignment that's the point
|
||||
if(codeVerifier) {
|
||||
cookies.set("code_verifier", codeVerifier, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10
|
||||
});
|
||||
}
|
||||
redirect(302, redir);
|
||||
}
|
||||
return async ({ cookies, params: { provider: providerID }, url }) => {
|
||||
let provider: Google | Discord | GitHub
|
||||
let scopes: string[]
|
||||
let noop = false
|
||||
switch (providerID) {
|
||||
case 'discord': {
|
||||
provider = discord
|
||||
scopes = ['identify']
|
||||
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')
|
||||
let redir: URL
|
||||
const state = generateState()
|
||||
let codeVerifier: string
|
||||
if (provider instanceof Google) {
|
||||
codeVerifier = generateCodeVerifier()
|
||||
redir = await provider.createAuthorizationURL(state, codeVerifier, {
|
||||
scopes
|
||||
})
|
||||
} else {
|
||||
redir = await provider.createAuthorizationURL(state, {
|
||||
scopes
|
||||
})
|
||||
}
|
||||
cookies.set('state', state, {
|
||||
secure: true,
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10
|
||||
})
|
||||
// @ts-expect-error: I know im using it before assignment that's the point
|
||||
if (codeVerifier) {
|
||||
cookies.set('code_verifier', codeVerifier, {
|
||||
secure: true,
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10
|
||||
})
|
||||
}
|
||||
redirect(302, redir)
|
||||
}
|
||||
}
|
||||
|
||||
export function oauth_callback(): ServerLoad<{ provider: string }, any, { type: "create" | "link", name: string, user: z.infer<typeof publicUser> }> {
|
||||
return async ({ cookies, params: { provider: providerID }, locals, url }) => {
|
||||
let provider: Google | Discord | GitHub;
|
||||
let scopes: string[]
|
||||
let noop = false;
|
||||
switch(providerID) {
|
||||
case "discord": {
|
||||
provider = discord
|
||||
scopes = ['identify']
|
||||
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");
|
||||
export function oauth_callback(): ServerLoad<
|
||||
{ provider: string },
|
||||
any,
|
||||
{
|
||||
type: 'create' | 'link'
|
||||
name: string
|
||||
user: z.infer<typeof publicUser>
|
||||
form: SuperForm<
|
||||
ValidationAdapter<
|
||||
{
|
||||
token: string
|
||||
},
|
||||
{
|
||||
token: string
|
||||
}
|
||||
>,
|
||||
any
|
||||
>
|
||||
prov: string
|
||||
}
|
||||
> {
|
||||
return async ({ cookies, params: { provider: providerID }, locals, url }) => {
|
||||
let provider: Google | Discord | GitHub
|
||||
let scopes: string[]
|
||||
let noop = false
|
||||
switch (providerID) {
|
||||
case 'discord': {
|
||||
provider = discord
|
||||
scopes = ['identify']
|
||||
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 storedCodeVerifier = cookies.get("code_verifier");
|
||||
if (!code || !storedState || state !== storedState || (provider instanceof Google && !storedCodeVerifier)) {
|
||||
error(400, "Invalid request")
|
||||
}
|
||||
let tokens
|
||||
if(provider instanceof Google) {
|
||||
tokens = await provider.validateAuthorizationCode(code, storedCodeVerifier)
|
||||
} else {
|
||||
tokens = await provider.validateAuthorizationCode(code)
|
||||
}
|
||||
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
|
||||
return {
|
||||
type: 'create',
|
||||
|
||||
}
|
||||
} 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
|
||||
|
||||
}
|
||||
}
|
||||
const storedState = cookies.get('state')
|
||||
const storedCodeVerifier = cookies.get('code_verifier')
|
||||
if (
|
||||
!code ||
|
||||
!storedState ||
|
||||
state !== storedState ||
|
||||
(provider instanceof Google && !storedCodeVerifier)
|
||||
) {
|
||||
error(400, 'Invalid request')
|
||||
}
|
||||
let tokens
|
||||
let id
|
||||
let name
|
||||
if (provider instanceof Google) {
|
||||
tokens = await provider.validateAuthorizationCode(code, storedCodeVerifier)
|
||||
console.log(tokens.idToken)
|
||||
const { sub, name: Uname } = decodeJwt(tokens.idToken)
|
||||
id = sub
|
||||
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 }> {
|
||||
return {
|
||||
|
||||
}
|
||||
export function oauth_callback_actions(): Actions<{ provider: string }> {
|
||||
return {}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import "../app.pcss"
|
||||
const { children } = $props()
|
||||
import '../app.pcss'
|
||||
const { children } = $props()
|
||||
</script>
|
||||
|
||||
{@render children()}
|
|
@ -1,5 +1,5 @@
|
|||
import { redirect } from "@sveltejs/kit";
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
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 }) {
|
||||
return hono.fetch(request, { locals })
|
||||
return hono.fetch(request, { locals })
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { publicUser } from '$lib/server/db.js';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { publicUser } from '$lib/server/db'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export async function load({ locals }) {
|
||||
if(!locals.session) {
|
||||
return redirect(302, "/auth")
|
||||
}
|
||||
return publicUser.safeParse(locals.user).data!
|
||||
if (!locals.session) {
|
||||
return redirect(302, '/auth')
|
||||
}
|
||||
return publicUser.safeParse(locals.user).data!
|
||||
}
|
||||
|
||||
export const csr = true;
|
||||
export const ssr = false;
|
||||
export const csr = true
|
||||
export const ssr = false
|
||||
|
|
|
@ -5,73 +5,92 @@
|
|||
</script>
|
||||
|
||||
<div class="h-[100vh] w-[100vw] flex flex-col">
|
||||
<div class="px-4">
|
||||
<div class="navbar bg-base-200 rounded-b-xl">
|
||||
<div class="flex-1">
|
||||
<button class="btn btn-ghost text-xl">spiel.place</button>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<details
|
||||
class="dropdown dropdown-end"
|
||||
ontoggle={({ newState }) => (dropdownOpen = newState === 'open')}>
|
||||
<summary class="btn btn-circle btn-ghost m-1 swap swap-rotate">
|
||||
<input type="checkbox" bind:checked={dropdownOpen} />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5 swap-off">
|
||||
<path
|
||||
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"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5 swap-on stroke-2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<ul class="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
|
||||
<li><a href="/app/settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<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" />
|
||||
</svg>
|
||||
{data.displayName}
|
||||
</a></li>
|
||||
<li>
|
||||
<a href="/auth/signout">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
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 class="px-4">
|
||||
<div class="navbar bg-base-200 rounded-b-xl">
|
||||
<div class="flex-1">
|
||||
<button class="btn btn-ghost text-xl">spiel.place</button>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<details
|
||||
class="dropdown dropdown-end"
|
||||
ontoggle={({ newState }) => (dropdownOpen = newState === 'open')}
|
||||
>
|
||||
<summary class="btn btn-circle btn-ghost m-1 swap swap-rotate">
|
||||
<input type="checkbox" bind:checked={dropdownOpen} />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5 swap-off"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5 swap-on stroke-2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<ul class="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
|
||||
<li>
|
||||
<a href="/app/settings">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<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"
|
||||
></path>
|
||||
</svg>
|
||||
{data.displayName}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/auth/signout">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
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 class="toast">
|
||||
<div class="alert alert-info">
|
||||
<span>New message arrived.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<span>New message arrived.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
<script>
|
||||
import { api } from '$lib/apiclient'
|
||||
import { goto } from "$app/navigation"
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
const { data } = $props()
|
||||
let roomname = $state('')
|
||||
const { data } = $props()
|
||||
let roomname = $state('')
|
||||
|
||||
async function createRoom() {
|
||||
const res = await api.rooms.create.$post({
|
||||
json: {
|
||||
name: roomname
|
||||
}
|
||||
})
|
||||
if(!res.ok) return
|
||||
const id = await res.text()
|
||||
goto(`/app/:${id}`)
|
||||
}
|
||||
async function createRoom() {
|
||||
const res = await api.rooms.create.$post({
|
||||
json: {
|
||||
name: roomname
|
||||
}
|
||||
})
|
||||
if (!res.ok) return
|
||||
const id = await res.text()
|
||||
goto(`/app/:${id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-56 flex flex-col justify-center">
|
||||
<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>
|
||||
<input bind:value={roomname} 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 class="my-10 px-2 py-2 rounded-r-lg bg-base-200">
|
||||
<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"
|
||||
/>
|
||||
<button on:click={createRoom} class="btn btn-secondary w-full">create</button>
|
||||
</div>
|
||||
</div>
|
||||
<main class="flex-grow">
|
||||
list
|
||||
</main>
|
||||
<main class="flex-grow">list</main>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
const { data } = $props()
|
||||
const { data } = $props()
|
||||
</script>
|
||||
|
||||
{data.roomID}
|
|
@ -1,3 +1,3 @@
|
|||
export async function load({ params: { roomID } }) {
|
||||
return { roomID }
|
||||
return { roomID }
|
||||
}
|
|
@ -1,75 +1,80 @@
|
|||
import { alphabet, generateRandomString } from 'oslo/crypto';
|
||||
import { fail, message, setError, superValidate } from 'sveltekit-superforms';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { z } from 'zod';
|
||||
import { hash, verify } from "@ts-rex/argon2"
|
||||
import { db } from '$lib/server/db.js';
|
||||
import { cookieController, cookieExpiration, createSessionForUser } from '$lib/server/auth.js';
|
||||
import { createDate } from 'oslo';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { alphabet, generateRandomString } from 'oslo/crypto'
|
||||
import { fail, message, setError, superValidate } from 'sveltekit-superforms'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { z } from 'zod'
|
||||
import { hash, verify } from '@ts-rex/argon2'
|
||||
import { db } from '$lib/server/db'
|
||||
import { cookieController, cookieExpiration, createSessionForUser } from '$lib/server/auth'
|
||||
import { createDate } from 'oslo'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
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 "-"`),
|
||||
password: z.string().min(8, "must be atleast 8 characters").max(255)
|
||||
});
|
||||
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 "-"`),
|
||||
password: z.string().min(8, 'must be atleast 8 characters').max(255)
|
||||
})
|
||||
|
||||
export async function load({ locals }) {
|
||||
if(locals.session) {
|
||||
return redirect(302, '/app')
|
||||
}
|
||||
const form = await superValidate(zod(schema));
|
||||
return { form };
|
||||
};
|
||||
if (locals.session) {
|
||||
return redirect(302, '/app')
|
||||
}
|
||||
const form = await superValidate(zod(schema))
|
||||
return { form }
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
login: async ({ request, cookies }) => {
|
||||
const form = await superValidate(request, zod(schema));
|
||||
login: async ({ request, cookies }) => {
|
||||
const form = await superValidate(request, zod(schema))
|
||||
|
||||
if (!form.valid) return fail(400, { form });
|
||||
const { username, password } = form.data
|
||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat();
|
||||
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?")
|
||||
const isvalid = verify(password, user.password);
|
||||
if (!isvalid) return setError(form, "incorrect password")
|
||||
const session = (await createSessionForUser(user.id))
|
||||
if (session.isSome()) {
|
||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
return redirect(302, '/app')
|
||||
} else {
|
||||
return fail(500, { form })
|
||||
}
|
||||
},
|
||||
signup: async ({ request, cookies }) => {
|
||||
const form = await superValidate(request, zod(schema));
|
||||
if (!form.valid) return fail(400, { form });
|
||||
const { username, password } = form.data
|
||||
if (!form.valid) return fail(400, { form })
|
||||
const { username, password } = form.data
|
||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
||||
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?')
|
||||
const isvalid = verify(password, user.password)
|
||||
if (!isvalid) return setError(form, 'incorrect password')
|
||||
const session = await createSessionForUser(user.id)
|
||||
if (session.isSome()) {
|
||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
return redirect(302, '/app')
|
||||
} else {
|
||||
return fail(500, { form })
|
||||
}
|
||||
},
|
||||
signup: async ({ request, cookies }) => {
|
||||
const form = await superValidate(request, zod(schema))
|
||||
if (!form.valid) return fail(400, { form })
|
||||
const { username, password } = form.data
|
||||
|
||||
const userId = generateRandomString(10, alphabet("0-9", "a-z"))
|
||||
const passwordHash = hash(password)
|
||||
const userId = generateRandomString(10, alphabet('0-9', 'a-z'))
|
||||
const passwordHash = hash(password)
|
||||
|
||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
||||
if (user) return setError(form, "username", 'username already exists')
|
||||
await db.user.set(userId, {
|
||||
displayName: username,
|
||||
username,
|
||||
id: userId,
|
||||
password: passwordHash
|
||||
})
|
||||
const session = (await createSessionForUser(userId))
|
||||
if (session.isSome()) {
|
||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
return redirect(302, '/app')
|
||||
} else {
|
||||
return fail(500, { form })
|
||||
}
|
||||
}
|
||||
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
|
||||
if (user) return setError(form, 'username', 'username already exists')
|
||||
await db.user.set(userId, {
|
||||
displayName: username,
|
||||
username,
|
||||
id: userId,
|
||||
password: passwordHash
|
||||
})
|
||||
const session = await createSessionForUser(userId)
|
||||
if (session.isSome()) {
|
||||
const sessionCookie = cookieController.createCookie(session.unwrap().id)
|
||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
})
|
||||
return redirect(302, '/app')
|
||||
} else {
|
||||
return fail(500, { form })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,9 +18,9 @@
|
|||
<div class="card bg-base-200 w-96 shadow-xl p-4">
|
||||
<h1 class="text-xl mb-2 font-bold">sign up/in</h1>
|
||||
<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
|
||||
class="size-4 brightness-50"
|
||||
class="size-4"
|
||||
viewBox="-3 0 262 262"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
|
@ -28,32 +28,36 @@
|
|||
><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"></g
|
||||
><g id="SVGRepo_iconCarrier"
|
||||
stroke-linejoin="round"
|
||||
></g><g id="SVGRepo_iconCarrier"
|
||||
><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"
|
||||
fill="#4285F4"></path
|
||||
><path
|
||||
fill="#4285F4"
|
||||
></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"
|
||||
fill="#34A853"></path
|
||||
><path
|
||||
fill="#34A853"
|
||||
></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"
|
||||
fill="#FBBC05"></path
|
||||
><path
|
||||
fill="#FBBC05"
|
||||
></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"
|
||||
fill="#EB4335"></path
|
||||
></g
|
||||
></svg>
|
||||
fill="#EB4335"
|
||||
></path></g
|
||||
></svg
|
||||
>
|
||||
</a>
|
||||
<a href="{$page.url.pathname}/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"
|
||||
<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"
|
||||
><path
|
||||
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"
|
||||
></path
|
||||
></svg>
|
||||
></path></svg
|
||||
>
|
||||
</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
|
||||
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"
|
||||
|
@ -62,24 +66,28 @@
|
|||
fill-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"
|
||||
fill="currentColor"></path
|
||||
></svg>
|
||||
fill="currentColor"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<span class="divider">or</span>
|
||||
<form class="flex-col flex gap-y-4" method="post" use:enhance>
|
||||
<label
|
||||
class="input input-bordered flex items-center gap-2"
|
||||
class:input-error={!!$errors.username}>
|
||||
class:input-error={!!$errors.username}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-4">
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
clip-rule="evenodd"></path>
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
|
||||
<input
|
||||
|
@ -88,23 +96,28 @@
|
|||
name="username"
|
||||
type="text"
|
||||
class="grow placeholder:text-base-content/20"
|
||||
placeholder="kaii" />
|
||||
placeholder="kaii"
|
||||
/>
|
||||
</label>
|
||||
<span
|
||||
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
|
||||
class="input input-bordered flex items-center gap-2"
|
||||
class:input-error={!!$errors.password}>
|
||||
class:input-error={!!$errors.password}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-4">
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
clip-rule="evenodd"></path>
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<input
|
||||
bind:value={password}
|
||||
|
@ -112,11 +125,13 @@
|
|||
name="password"
|
||||
type="password"
|
||||
class="grow placeholder:text-base-content/20"
|
||||
placeholder="verygoodpassword" />
|
||||
placeholder="verygoodpassword"
|
||||
/>
|
||||
</label>
|
||||
<span
|
||||
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">
|
||||
<button formaction="?/signup" type="submit" class="btn btn-secondary flex-grow">
|
||||
sign up
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { cookieController, deleteSession } from '$lib/server/auth';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { cookieController, deleteSession } from '$lib/server/auth'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export async function GET({ locals, cookies }) {
|
||||
if(locals.session) {
|
||||
await deleteSession(locals.session.id)
|
||||
const sessionCookie = cookieController.createBlankCookie();
|
||||
if (locals.session) {
|
||||
await deleteSession(locals.session.id)
|
||||
const sessionCookie = cookieController.createBlankCookie()
|
||||
cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
path: '.',
|
||||
...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 { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import adapter from 'sveltekit-adapter-deno'
|
||||
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
|
||||
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} */
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [daisyui],
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: [daisyui]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
|
@ -11,4 +11,4 @@ export default defineConfig({
|
|||
target: ['deno1.45.5']
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue