/* * This file is a part of the SteamWar software. * * Copyright (C) 2025 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import {readable, writable} from "svelte/store"; import {tokenStore} from "@repo/repo.ts"; import dayjs, {type Dayjs} from "dayjs"; import {type AuthToken, AuthTokenSchema} from "@type/auth.ts"; export class AuthV2Repo { private accessToken: string | undefined; private accessTokenExpires: Dayjs | undefined; private refreshToken: string | undefined; private refreshTokenExpires: Dayjs | undefined; constructor() { if (typeof localStorage === "undefined") { return; } this.accessToken = localStorage.getItem("sw-access-token") ?? undefined; if (this.accessToken) { this.accessTokenExpires = dayjs(localStorage.getItem("sw-access-token-expires") ?? ""); } this.refreshToken = localStorage.getItem("sw-refresh-token") ?? undefined; if (this.refreshToken) { loggedIn.set(true); this.refreshTokenExpires = dayjs(localStorage.getItem("sw-refresh-token-expires") ?? ""); } } async login(name: string, password: string) { if (this.accessToken !== undefined || this.refreshToken !== undefined) { throw new Error("Already logged in"); } try { const login = await this.request("/auth/state", { method: "POST", body: JSON.stringify({ name, password, keepLoggedIn: true, }), }).then(value => value.json()).then(value => AuthTokenSchema.parse(value)); this.setLoginState(login); return true; } catch (e) { return false; } } async resetPassword(name: string, password: string, token: string) { return await this.requestWithToken(token, "/auth/register", { method: "POST", body: JSON.stringify({ name, password, }), }); } async logout() { if (this.accessToken === undefined) { return; } await this.request("/auth/state", { method: "DELETE", }); this.resetAccessToken(); this.resetRefreshToken(); } private setLoginState(tokens: AuthToken) { this.setAccessToken(tokens.accessToken.token, dayjs(tokens.accessToken.expires)); this.setRefreshToken(tokens.refreshToken.token, dayjs(tokens.refreshToken.expires)); loggedIn.set(true); } private setAccessToken(token: string, expires: Dayjs) { this.accessToken = token; this.accessTokenExpires = expires; localStorage.setItem("sw-access-token", token); localStorage.setItem("sw-access-token-expires", expires.toString()); } private resetAccessToken() { if (this.accessToken === undefined) { return; } this.accessToken = undefined; this.accessTokenExpires = undefined; localStorage.removeItem("sw-access-token"); localStorage.removeItem("sw-access-token-expires"); } private setRefreshToken(token: string, expires: Dayjs) { this.refreshToken = token; this.refreshTokenExpires = expires; localStorage.setItem("sw-refresh-token", token); localStorage.setItem("sw-refresh-token-expires", expires.toString()); } private resetRefreshToken() { if (this.refreshToken === undefined) { return; } this.refreshToken = undefined; this.refreshTokenExpires = undefined; localStorage.removeItem("sw-refresh-token"); localStorage.removeItem("sw-refresh-token-expires"); loggedIn.set(false); } private async refresh() { if (this.refreshToken === undefined || this.refreshTokenExpires === undefined || this.refreshTokenExpires.isBefore(dayjs().add(10, "seconds"))) { this.resetRefreshToken(); this.resetAccessToken(); return; } try { const response = await this.requestWithToken(this.refreshToken!, "/auth/state", { method: "PUT", }).then(value => value.json()).then(value => AuthTokenSchema.parse(value)); this.setLoginState(response); } catch (e) { this.resetRefreshToken(); this.resetAccessToken(); return; } } async request(url: string, params: RequestInit = {}) { if (this.accessToken !== undefined && this.accessTokenExpires !== undefined && this.accessTokenExpires.isBefore(dayjs().add(10, "seconds"))) { await this.refresh(); } return this.requestWithToken(this.accessToken ?? "", url, params); } private async requestWithToken(token: string, url: string, params: RequestInit = {}) { return fetch(`${import.meta.env.PUBLIC_API_SERVER}${url}`, {...params, headers: { ...(token !== "" ? {"Authorization": "Bearer " + (token)} : {}), "Content-Type": "application/json", ...params.headers, }, }) .then(value => { if (value.status === 401) { tokenStore.set(""); } return value; }); } } export const loggedIn = writable(false); export const authV2Repo = readable(new AuthV2Repo());