implemented automated user session login and session refresh
This commit is contained in:
parent
976cd3edb9
commit
16b5aba457
|
|
@ -0,0 +1,77 @@
|
|||
import type { Handle } from "@sveltejs/kit"
|
||||
|
||||
import { error, redirect, json, fail } from "@sveltejs/kit"
|
||||
|
||||
import { Error401Cause } from "$lib/errors"
|
||||
|
||||
import Config from "$lib/server/config"
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
|
||||
function action_fail<T = undefined>(status: number, data: T) {
|
||||
return json(fail(status, data))
|
||||
}
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
const token = event.cookies.get("session")
|
||||
let session = await UserMgmt.session_login(token ?? "")
|
||||
|
||||
if (session && event.route.id !== "/api/refresh") {
|
||||
if (session.expires.getTime() < Date.now() + Config.session_refresh_grace) {
|
||||
|
||||
const response = await event.fetch("/api/refresh", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: token
|
||||
})
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const new_session = await response.json()
|
||||
|
||||
if (new_session && new_session.token !== null && new_session.expires !== null) {
|
||||
event.cookies.set("session", new_session.token, {
|
||||
expires: new Date(new_session.expires),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
path: "/"
|
||||
})
|
||||
// For mobile
|
||||
/*event.setHeaders({
|
||||
"X-Authorization-Refresh": new_session.token
|
||||
})*/
|
||||
|
||||
session = await UserMgmt.session_login(new_session.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.locals.user = session?.user
|
||||
|
||||
if (!event.route.id) {
|
||||
return error(404, { message: "Diese Seite existiert nicht" })
|
||||
}
|
||||
|
||||
if (event.route.id.startsWith("/api")) {
|
||||
if (event.route.id.startsWith("/api/users") && !event.locals.user) {
|
||||
return error(401, { cause: Error401Cause.NotLoggedIn, message: "Please log in" })
|
||||
}
|
||||
} else {
|
||||
if (!event.locals.user && !event.route.id.startsWith("/login")) {
|
||||
if (event.request.method === "POST") {
|
||||
if (event.request.headers.get("x-sveltekit-action")) {
|
||||
return action_fail(401, { cause: Error401Cause.NotLoggedIn, message: "Bitte melden Sie sich an." })
|
||||
}
|
||||
return error(401, { cause: Error401Cause.NotLoggedIn, message: "Bitte melden Sie sich an." })
|
||||
}
|
||||
return redirect(307, "/login")
|
||||
}
|
||||
}
|
||||
|
||||
return await resolve(event)
|
||||
}
|
||||
|
|
@ -14,6 +14,10 @@ export class DuplicateError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export const enum Error401Cause {
|
||||
NotLoggedIn
|
||||
}
|
||||
|
||||
export const enum RegisterResponseCause {
|
||||
Server = 1,
|
||||
MalformedRequest,
|
||||
|
|
@ -32,3 +36,10 @@ export const enum LoginResponseCause {
|
|||
NotFound,
|
||||
Timeout
|
||||
}
|
||||
|
||||
export const enum RefreshResponseCause {
|
||||
Server = 1,
|
||||
MalformedRequest,
|
||||
InvalidToken,
|
||||
InvalidSession
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class Config {
|
|||
readonly is_production: boolean = process.env.NODE_ENV == "production"
|
||||
|
||||
private _session_timeout: number = 15 * 60 * 1000
|
||||
private _session_refresh_grace: number = 5 * 60 * 1000 // time until expiration
|
||||
|
||||
get log_dir(): string {
|
||||
return this._log_dir
|
||||
|
|
@ -47,6 +48,7 @@ class Config {
|
|||
}
|
||||
|
||||
get session_timeout(): number { return this._session_timeout }
|
||||
get session_refresh_grace(): number { return this._session_refresh_grace }
|
||||
|
||||
constructor() {
|
||||
this._log_dir = resolve_env_to_path(process.env.APP_LOG_DIR, "./data/logs")
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ class Cache {
|
|||
}
|
||||
}
|
||||
|
||||
invalidate_session(session: SessionData) {
|
||||
this._session_map.delete(session.token)
|
||||
}
|
||||
|
||||
get_session(token: string): SessionData|null {
|
||||
const session_info = this._session_map.get(token)
|
||||
if (!session_info) return null
|
||||
|
|
@ -118,14 +122,39 @@ class UserMgmt {
|
|||
return null
|
||||
}
|
||||
|
||||
const token = Crypto.randomBytes(32).toBase64()
|
||||
const session_info = this._cache.add(user, token, new Date(), new Date(Date.now() + Config.session_timeout))
|
||||
const session_info = this._generate_session_for_user(user)
|
||||
|
||||
return session_info
|
||||
}
|
||||
|
||||
async session_login(token: string): Promise<SessionData|null> {
|
||||
return this._cache.get_session(token)
|
||||
const session = this._cache.get_session(token)
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
if (session?.expires.getTime() < Date.now()) {
|
||||
return null
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
async refresh_session(token: string): Promise<SessionData|null> {
|
||||
const session = await this.session_login(token)
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
const new_session = this._generate_session_for_user(session.user)
|
||||
this._cache.invalidate_session(session)
|
||||
|
||||
return new_session
|
||||
}
|
||||
|
||||
private _generate_session_for_user(user: User): SessionData {
|
||||
const token = Crypto.randomBytes(32).toBase64()
|
||||
const session_info = this._cache.add(user, token, new Date(), new Date(Date.now() + Config.session_timeout))
|
||||
|
||||
return session_info
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import type { RequestHandler } from "./$types"
|
||||
|
||||
import { error, json } from "@sveltejs/kit"
|
||||
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
import { RefreshResponseCause } from "$lib/errors"
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
|
||||
const data = await request.json()
|
||||
|
||||
const token = data["token"]
|
||||
if (!token || typeof token !== "string") {
|
||||
return error(400, { cause: RefreshResponseCause.MalformedRequest, message: "token must be provided as string." })
|
||||
}
|
||||
|
||||
const new_session = await UserMgmt.refresh_session(token)
|
||||
|
||||
if (!new_session) {
|
||||
return error(401, { cause: RefreshResponseCause.InvalidSession, message: "No session for token" })
|
||||
}
|
||||
|
||||
return json({
|
||||
token: new_session.token,
|
||||
expires: new_session.expires
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue