frontend cookie implementation

This commit is contained in:
Hayden 2023-02-17 19:00:50 -09:00
parent bd321af29f
commit 889686ecfa
No known key found for this signature in database
GPG key ID: 17CF79474E257545
7 changed files with 145 additions and 31 deletions

View file

@ -1,11 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useAuthStore } from "~~/stores/auth"; const ctx = useAuthContext();
const authStore = useAuthStore();
const api = useUserApi(); const api = useUserApi();
async function logout() { async function logout() {
const { error } = await authStore.logout(api); const { error } = await ctx.logout(api);
if (error) { if (error) {
return; return;
} }

View file

@ -1,7 +1,6 @@
import { PublicApi } from "~~/lib/api/public"; import { PublicApi } from "~~/lib/api/public";
import { UserClient } from "~~/lib/api/user"; import { UserClient } from "~~/lib/api/user";
import { Requests } from "~~/lib/requests"; import { Requests } from "~~/lib/requests";
import { useAuthStore } from "~~/stores/auth";
export type Observer = { export type Observer = {
handler: (r: Response, req?: RequestInit) => void; handler: (r: Response, req?: RequestInit) => void;
@ -29,13 +28,13 @@ export function usePublicApi(): PublicApi {
} }
export function useUserApi(): UserClient { export function useUserApi(): UserClient {
const authStore = useAuthStore(); const authCtx = useAuthContext();
const requests = new Requests("", () => authStore.token, {}); const requests = new Requests("", () => authCtx.token || "", {});
requests.addResponseInterceptor(logger); requests.addResponseInterceptor(logger);
requests.addResponseInterceptor(r => { requests.addResponseInterceptor(r => {
if (r.status === 401) { if (r.status === 401) {
authStore.clearSession(); authCtx.invalidateSession();
} }
}); });
@ -43,5 +42,5 @@ export function useUserApi(): UserClient {
requests.addResponseInterceptor(observer.handler); requests.addResponseInterceptor(observer.handler);
} }
return new UserClient(requests, authStore.attachmentToken); return new UserClient(requests, authCtx.attachmentToken || "");
} }

View file

@ -0,0 +1,128 @@
import { CookieRef } from "nuxt/dist/app/composables";
import { PublicApi } from "~~/lib/api/public";
import { UserOut } from "~~/lib/api/types/data-contracts";
import { UserClient } from "~~/lib/api/user";
export interface IAuthContext {
get token(): string | null;
get expiresAt(): string | null;
get attachmentToken(): string | null;
/**
* The current user object for the session. This is undefined if the session is not authorized.
*/
user?: UserOut;
/**
* Returns true if the session is expired.
*/
isExpired(): boolean;
/**
* Returns true if the session is authorized.
*/
isAuthorized(): boolean;
/**
* Invalidates the session by removing the token and the expiresAt.
*/
invalidateSession(): void;
/**
* Logs out the user and calls the invalidateSession method.
*/
logout(api: UserClient): ReturnType<UserClient["user"]["logout"]>;
/**
* Logs in the user and sets the authorization context via cookies
*/
login(api: PublicApi, email: string, password: string): ReturnType<PublicApi["login"]>;
}
class AuthContext implements IAuthContext {
user?: UserOut;
private _token: CookieRef<string | null>;
private _expiresAt: CookieRef<string | null>;
private _attachmentToken: CookieRef<string | null>;
get token() {
return this._token.value;
}
get expiresAt() {
return this._expiresAt.value;
}
get attachmentToken() {
return this._attachmentToken.value;
}
constructor(
token: CookieRef<string | null>,
expiresAt: CookieRef<string | null>,
attachmentToken: CookieRef<string | null>
) {
this._token = token;
this._expiresAt = expiresAt;
this._attachmentToken = attachmentToken;
}
isExpired() {
const expiresAt = this.expiresAt;
if (expiresAt === null) {
return true;
}
const expiresAtDate = new Date(expiresAt);
const now = new Date();
return now.getTime() > expiresAtDate.getTime();
}
isAuthorized() {
return this._token.value !== null && !this.isExpired();
}
invalidateSession() {
this.user = undefined;
this._token.value = null;
this._expiresAt.value = null;
this._attachmentToken.value = null;
}
async login(api: PublicApi, email: string, password: string) {
const r = await api.login(email, password);
if (!r.error) {
this._token.value = r.data.token;
this._expiresAt.value = r.data.expiresAt as string;
this._attachmentToken.value = r.data.attachmentToken;
console.log({
token: this._token.value,
expiresAt: this._expiresAt.value,
attachmentToken: this._attachmentToken.value,
});
}
return r;
}
async logout(api: UserClient) {
const r = await api.user.logout();
if (!r.error) {
this.invalidateSession();
}
return r;
}
}
export function useAuthContext(): IAuthContext {
const tokenCookie = useCookie("hb.auth.token");
const expiresAtCookie = useCookie("hb.auth.expires_at");
const attachmentTokenCookie = useCookie("hb.auth.attachment_token");
return new AuthContext(tokenCookie, expiresAtCookie, attachmentTokenCookie);
}

View file

@ -91,11 +91,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useAuthStore } from "~~/stores/auth";
import { useLabelStore } from "~~/stores/labels"; import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations"; import { useLocationStore } from "~~/stores/locations";
const username = computed(() => authStore.self?.name || "User"); const username = computed(() => authCtx.self?.name || "User");
// Preload currency format // Preload currency format
useFormatCurrency(); useFormatCurrency();
@ -223,11 +222,11 @@
eventBus.off(EventTypes.InvalidStores, "stores"); eventBus.off(EventTypes.InvalidStores, "stores");
}); });
const authStore = useAuthStore(); const authCtx = useAuthContext();
const api = useUserApi(); const api = useUserApi();
async function logout() { async function logout() {
const { error } = await authStore.logout(api); const { error } = await authCtx.logout(api);
if (error) { if (error) {
return; return;
} }

View file

@ -1,15 +1,13 @@
import { useAuthStore } from "~~/stores/auth";
export default defineNuxtRouteMiddleware(async () => { export default defineNuxtRouteMiddleware(async () => {
const auth = useAuthStore(); const ctx = useAuthContext();
const api = useUserApi(); const api = useUserApi();
if (!auth.self) { if (!ctx.user) {
const { data, error } = await api.user.self(); const { data, error } = await api.user.self();
if (error) { if (error) {
navigateTo("/"); navigateTo("/");
} }
auth.$patch({ self: data.item }); ctx.user = data.item;
} }
}); });

View file

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from "~~/stores/auth";
useHead({ useHead({
title: "Homebox | Organize and Tag Your Stuff", title: "Homebox | Organize and Tag Your Stuff",
}); });
@ -8,6 +7,8 @@
layout: "empty", layout: "empty",
}); });
const ctx = useAuthContext();
const api = usePublicApi(); const api = usePublicApi();
const toast = useNotifier(); const toast = useNotifier();
@ -28,8 +29,7 @@
} }
}); });
const authStore = useAuthStore(); if (!ctx.isAuthorized()) {
if (!authStore.isTokenExpired) {
navigateTo("/home"); navigateTo("/home");
} }
@ -91,7 +91,7 @@
async function login() { async function login() {
loading.value = true; loading.value = true;
const { data, error } = await api.login(email.value, loginPassword.value); const { error } = await ctx.login(api, email.value, loginPassword.value);
if (error) { if (error) {
toast.error("Invalid email or password"); toast.error("Invalid email or password");
@ -101,13 +101,6 @@
toast.success("Logged in successfully"); toast.success("Logged in successfully");
// @ts-ignore
authStore.$patch({
token: data.token,
expires: data.expiresAt,
attachmentToken: data.attachmentToken,
});
navigateTo("/home"); navigateTo("/home");
loading.value = false; loading.value = false;
} }

View file

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Detail } from "~~/components/global/DetailsSection/types"; import { Detail } from "~~/components/global/DetailsSection/types";
import { useAuthStore } from "~~/stores/auth";
import { themes } from "~~/lib/data/themes"; import { themes } from "~~/lib/data/themes";
import { currencies, Currency } from "~~/lib/data/currency"; import { currencies, Currency } from "~~/lib/data/currency";
@ -79,7 +78,7 @@
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const auth = useAuthStore(); const auth = useAuthContext();
const details = computed(() => { const details = computed(() => {
return [ return [