further improve cookie handling

this is a bit messy but it works soooooo
This commit is contained in:
MiniDigger | Martin 2022-06-15 12:23:20 +02:00
parent ed404fc6bf
commit 80d0fde4cb
3 changed files with 55 additions and 14 deletions

View File

@ -8,6 +8,7 @@ import { Ref } from "vue";
import { authLog, fetchLog } from "~/composables/useLog";
import { isEmpty } from "lodash-es";
import { useAuth } from "~/composables/useAuth";
import { useResponse } from "~/composables/useResReq";
interface StatCookie {
// eslint-disable-next-line camelcase
@ -53,7 +54,8 @@ function request<T>(url: string, method: AxiosRequestConfig["method"], data: obj
resolve(data);
})
.catch(async (error: AxiosError) => {
authLog("failed", error);
const { trace, ...err } = error.response?.data as object;
authLog("failed", err);
// do we have an expired token?
if (error.response?.status === 403) {
if (retry) {
@ -67,6 +69,10 @@ function request<T>(url: string, method: AxiosRequestConfig["method"], data: obj
if (result) {
// retry
authLog("Retrying request...");
if (typeof result === "string") {
headers = { ...headers, Authorization: `HangarAuth ${result}` };
authLog("using new token");
}
try {
const response = await request<T>(url, method, data, headers, true);
return resolve(response);
@ -106,10 +112,17 @@ export async function useInternalApi<T>(
return processAuthStuff(headers, authed, (headers) => request(`internal/${url}`, method, data, headers));
}
function processAuthStuff<T>(headers: Record<string, string>, authRequired: boolean, handler: (headers: Record<string, string>) => Promise<T>) {
export function processAuthStuff<T>(headers: Record<string, string>, authRequired: boolean, handler: (headers: Record<string, string>) => Promise<T>) {
if (import.meta.env.SSR) {
// forward cookies I guess?
const token = useCookies().get("HangarAuth");
let token = useCookies().get("HangarAuth");
if (!token) {
const header = useResponse()?.getHeader("set-cookie") as string[];
if (header) {
token = new Cookies(header.join("; ")).get("HangarAuth");
authLog("found token in set-cookie header");
}
}
if (token) {
authLog("forward token from cookie");
headers = { ...headers, Authorization: `HangarAuth ${token}` };

View File

@ -1,11 +1,14 @@
import { useAuthStore } from "~/store/auth";
import { useCookies } from "~/composables/useCookies";
import { useContext } from "vite-ssr";
import { useInternalApi } from "~/composables/useApi";
import { processAuthStuff, useInternalApi } from "~/composables/useApi";
import { useAxios } from "~/composables/useAxios";
import { authLog } from "~/composables/useLog";
import { HangarUser } from "hangar-internal";
import { useRequest } from "~/composables/useResReq";
import * as domain from "~/composables/useDomain";
import { Pinia } from "pinia";
import { AxiosError, AxiosRequestHeaders } from "axios";
import { useResponse } from "~/composables/useResReq";
import Cookies from "universal-cookie";
class Auth {
loginUrl(redirectUrl: string): string {
@ -20,7 +23,7 @@ class Auth {
}
// TODO do we need to scope this to the user?
refreshPromise: Promise<boolean> | null = null;
refreshPromise: Promise<boolean | string> | null = null;
async refreshToken() {
authLog("refresh token");
@ -32,16 +35,40 @@ class Auth {
}
// eslint-disable-next-line no-async-promise-executor
this.refreshPromise = new Promise<boolean>(async (resolve) => {
this.refreshPromise = new Promise<boolean | string>(async (resolve) => {
try {
authLog("do request");
await useAxios.get("/refresh");
authLog("done");
resolve(true);
const headers: AxiosRequestHeaders = {};
if (import.meta.env.SSR) {
headers.cookie = "HangarAuth_REFRESH=" + useCookies().get("HangarAuth_REFRESH");
authLog("pass refresh cookie");
}
const response = await useAxios.get("/refresh", { headers });
if (import.meta.env.SSR) {
if (response.headers["set-cookie"]) {
useResponse()?.setHeader("set-cookie", response.headers["set-cookie"]!);
const token = new Cookies(response.headers["set-cookie"]?.join("; ")).get("HangarAuth");
if (token) {
authLog("got token");
resolve(token);
} else {
authLog("got no token in cookie header", response.headers["set-cookie"]);
resolve(false);
}
} else {
authLog("got no cookie header back");
resolve(false);
}
} else {
authLog("done");
resolve(true);
}
this.refreshPromise = null;
} catch (e) {
authLog("Refresh failed", e);
const { trace, ...err } = (e as AxiosError).response?.data as object;
authLog("Refresh failed", err);
resolve(false);
this.refreshPromise = null;
}
});
return this.refreshPromise;
@ -62,7 +89,7 @@ class Auth {
}
async updateUser(): Promise<void> {
const user = await useInternalApi<HangarUser>("users/@me").catch(async (err) => {
const user = await useInternalApi<HangarUser>("users/@me", false).catch(async (err) => {
authLog("no user");
return this.invalidate();
});
@ -76,7 +103,7 @@ class Auth {
}
usePiniaIfPresent() {
return import.meta.env.SSR ? useRequest()?.pinia : null;
return import.meta.env.SSR ? domain.get<Pinia>("pinia") : null;
}
}

View File

@ -43,6 +43,7 @@ export default viteSSR(App, options, async (ctx) => {
const head = createHead();
const pinia = createPinia();
app.use(pinia).use(head);
domain.set("pinia", pinia);
// install all modules under `modules/`
for (const module of Object.values(import.meta.globEager("./modules/*.ts"))) {