mirror of
https://github.com/vcscsvcscs/GenerationsHeritage.git
synced 2025-08-14 23:09:07 +02:00
init oauth and db
This commit is contained in:
@@ -1,4 +1,32 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { i18n } from '$lib/i18n';
|
||||
import { validateSessionToken, setSessionTokenCookie, deleteSessionTokenCookie } from "$lib/server/session";
|
||||
import { sequence } from "@sveltejs/kit/hooks";
|
||||
|
||||
const handleParaglide: Handle = i18n.handle();
|
||||
export const handle: Handle = handleParaglide;
|
||||
|
||||
const authHandle: Handle = async ({ event, resolve }) => {
|
||||
const token = event.cookies.get("session") ?? null;
|
||||
if (token === null) {
|
||||
event.locals.session = null;
|
||||
return resolve(event);
|
||||
}
|
||||
|
||||
if(!event.platform || !event.platform.env || !event.platform.env.GH_SESSIONS){
|
||||
return new Response("Server configuration error. GH_SESSIONS KeyValue store missing", {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
const session = await validateSessionToken(token, event.platform.env.GH_SESSIONS);
|
||||
if (session !== null) {
|
||||
setSessionTokenCookie(event, token, session.expiresAt);
|
||||
} else {
|
||||
deleteSessionTokenCookie(event);
|
||||
}
|
||||
|
||||
event.locals.session = session;
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
export const handle: Handle = sequence(handleParaglide, authHandle);
|
11
app/src/lib/server/db.ts
Normal file
11
app/src/lib/server/db.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import memgraph from 'neo4j-driver';
|
||||
import type { Driver } from 'neo4j-driver';
|
||||
import { MEMEGRAPH_URI, MEMGRAPH_USER, MEMGRAPH_PASSWORD } from '$env/static/private';
|
||||
|
||||
export const driverInstance: Driver = memgraph.driver(
|
||||
MEMEGRAPH_URI || 'bolt://localhost:7687',
|
||||
memgraph.auth.basic(
|
||||
MEMGRAPH_USER || 'memgraph',
|
||||
MEMGRAPH_PASSWORD || 'memgraph'
|
||||
)
|
||||
);
|
4
app/src/lib/server/oauth.ts
Normal file
4
app/src/lib/server/oauth.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Google } from "arctic";
|
||||
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URI } from "$env/static/private";
|
||||
|
||||
export const google = new Google(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URI || "http://localhost:5173/login/google/callback");
|
37
app/src/lib/server/user.ts
Normal file
37
app/src/lib/server/user.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Session } from 'neo4j-driver';
|
||||
|
||||
export function createUser(db: Session, googleId: string, email: string, first_name: string, family_name: string, middle_name: string): User {
|
||||
const row = db.run
|
||||
if (row === null) {
|
||||
throw new Error("Unexpected error");
|
||||
}
|
||||
const user: User = {
|
||||
id: row.number(0),
|
||||
googleId,
|
||||
email,
|
||||
};
|
||||
return user;
|
||||
}
|
||||
|
||||
export function getUserFromGoogleId(googleId: string): User | null {
|
||||
const row = db.queryOne("SELECT id, google_id, email, name, picture FROM user WHERE google_id = ?", [googleId]);
|
||||
if (row === null) {
|
||||
return null;
|
||||
}
|
||||
const user: User = {
|
||||
id: row.number(0),
|
||||
googleId: row.string(1),
|
||||
email: row.string(2),
|
||||
name: row.string(3),
|
||||
picture: row.string(4)
|
||||
};
|
||||
return user;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
googleId: string;
|
||||
name: string;
|
||||
picture: string;
|
||||
}
|
32
app/src/routes/+page.server.ts
Normal file
32
app/src/routes/+page.server.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
import { deleteSessionTokenCookie, invalidateSession } from "$lib/server/session";
|
||||
import type { Actions, RequestEvent } from "./$types";
|
||||
|
||||
export async function load(event: RequestEvent) {
|
||||
if (event.locals.session === null /*|| event.locals.familytree === nul*/) {
|
||||
return redirect(302, "/login");
|
||||
}
|
||||
return {
|
||||
// TODO - Add Family Graph
|
||||
};
|
||||
}
|
||||
|
||||
export const actions: Actions = {
|
||||
logout: logout
|
||||
};
|
||||
|
||||
async function logout(event: RequestEvent) {
|
||||
if (event.locals.session === null) {
|
||||
return fail(401);
|
||||
}
|
||||
|
||||
if (event.platform && event.platform.env && event.platform.env.GH_SESSIONS) {
|
||||
invalidateSession(event.locals.session.id, event.platform.env.GH_SESSIONS);
|
||||
} else {
|
||||
return fail(500, { message: "Server configuration error" });
|
||||
}
|
||||
|
||||
deleteSessionTokenCookie(event);
|
||||
|
||||
return redirect(302, "/login");
|
||||
}
|
@@ -3,7 +3,7 @@ import { redirect } from "@sveltejs/kit";
|
||||
import type { RequestEvent } from "./$types";
|
||||
|
||||
export async function load(event: RequestEvent) {
|
||||
if (event.locals.session !== null && event.locals.user !== null) {
|
||||
if (event.locals.session !== null) {
|
||||
return redirect(302, "/");
|
||||
}
|
||||
return {};
|
||||
|
23
app/src/routes/login/+page.svelte
Normal file
23
app/src/routes/login/+page.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import {sign_in,title} from '$lib/paraglide/messages.js';
|
||||
import Google from "./web_neutral_rd_SI.svg";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{title({ page: sign_in() })}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="hero bg-base-200 min-h-screen">
|
||||
<div class="hero-content flex flex-col items-center justify-center text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">{sign_in()}</h1>
|
||||
<p class="py-6">
|
||||
Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem
|
||||
quasi. In deleniti eaque aut repudiandae et a id nisi.
|
||||
</p>
|
||||
<a href="/login/google" class="inline-block">
|
||||
<img src="{Google}" alt="{sign_in()}" class="w-full h-auto">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
32
app/src/routes/login/google/+server.ts
Normal file
32
app/src/routes/login/google/+server.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { google } from "$lib/server/oauth";
|
||||
import { generateCodeVerifier, generateState } from "arctic";
|
||||
|
||||
import type { RequestEvent } from "./$types";
|
||||
|
||||
export function GET(event: RequestEvent): Response {
|
||||
const state = generateState();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const url = google.createAuthorizationURL(state, codeVerifier, ["openid", "profile", "email"]);
|
||||
|
||||
event.cookies.set("google_oauth_state", state, {
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10,
|
||||
secure: import.meta.env.PROD,
|
||||
path: "/",
|
||||
sameSite: "lax"
|
||||
});
|
||||
event.cookies.set("google_code_verifier", codeVerifier, {
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10,
|
||||
secure: import.meta.env.PROD,
|
||||
path: "/",
|
||||
sameSite: "lax"
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: url.toString()
|
||||
}
|
||||
});
|
||||
}
|
81
app/src/routes/login/google/callback/+server.ts
Normal file
81
app/src/routes/login/google/callback/+server.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { fail } from "@sveltejs/kit";
|
||||
import { google } from "$lib/server/oauth";
|
||||
import { ObjectParser } from "@pilcrowjs/object-parser";
|
||||
import { createUser, getUserFromGoogleId } from "$lib/server/user";
|
||||
import { driverInstance } from "$lib/server/db";
|
||||
import { createSession, generateSessionToken, setSessionTokenCookie } from "$lib/server/session";
|
||||
import { decodeIdToken } from "arctic";
|
||||
|
||||
import type { RequestEvent } from "./$types";
|
||||
import type { OAuth2Tokens } from "arctic";
|
||||
import { middle_name } from "$lib/paraglide/messages";
|
||||
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
const storedState = event.cookies.get("google_oauth_state") ?? null;
|
||||
const codeVerifier = event.cookies.get("google_code_verifier") ?? null;
|
||||
const code = event.url.searchParams.get("code");
|
||||
const state = event.url.searchParams.get("state");
|
||||
|
||||
if (storedState === null || codeVerifier === null || code === null || state === null) {
|
||||
return new Response("Please restart the process.", {
|
||||
status: 400
|
||||
});
|
||||
}
|
||||
if (storedState !== state) {
|
||||
return new Response("Please restart the process.", {
|
||||
status: 400
|
||||
});
|
||||
}
|
||||
|
||||
let tokens: OAuth2Tokens;
|
||||
try {
|
||||
tokens = await google.validateAuthorizationCode(code, codeVerifier);
|
||||
} catch (e) {
|
||||
return new Response("Please restart the process.", {
|
||||
status: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (!event.platform || !event.platform.env || !event.platform.env.GH_SESSIONS) {
|
||||
return new Response("Server configuration error. GH_SESSIONS KeyValue store missing", {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
const claims = decodeIdToken(tokens.idToken());
|
||||
const claimsParser = new ObjectParser(claims);
|
||||
|
||||
const googleId = claimsParser.getString("sub");
|
||||
const family_name = claimsParser.getString("family_name");
|
||||
const first_name = claimsParser.getString("given_name");
|
||||
const middle_name = claimsParser.getString("middle_name");
|
||||
const picture = claimsParser.getString("picture");
|
||||
const email = claimsParser.getString("email");
|
||||
|
||||
const existingUser = getUserFromGoogleId(googleId);
|
||||
if (existingUser !== null) {
|
||||
const sessionToken = generateSessionToken();
|
||||
const session = await createSession(sessionToken, existingUser.id, event.platform.env.GH_SESSIONS);
|
||||
setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: "/"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const dbSession = driverInstance.session()
|
||||
const user = createUser(dbSession, googleId, email, first_name, family_name, middle_name);
|
||||
dbSession.close();
|
||||
const sessionToken = generateSessionToken();
|
||||
const session = await createSession(sessionToken, user.id, event.platform.env.GH_SESSIONS);
|
||||
setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: "/"
|
||||
}
|
||||
});
|
||||
}
|
15
app/src/routes/login/web_neutral_rd_SI.svg
Normal file
15
app/src/routes/login/web_neutral_rd_SI.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
Reference in New Issue
Block a user