Refresh token to check if user was verifed after a 403

This commit is contained in:
Zoe Roux 2024-03-10 17:54:43 +01:00
parent 5f936d36b1
commit 8ee4446b30
2 changed files with 27 additions and 11 deletions

View File

@ -20,7 +20,7 @@
import { queryFn } from "./query"; import { queryFn } from "./query";
import { KyooErrors } from "./kyoo-errors"; import { KyooErrors } from "./kyoo-errors";
import { Account, TokenP, getCurrentApiUrl } from "./accounts"; import { Account, Token, TokenP, getCurrentApiUrl } from "./accounts";
import { UserP } from "./resources"; import { UserP } from "./resources";
import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal"; import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
import { Platform } from "react-native"; import { Platform } from "react-native";
@ -88,16 +88,21 @@ export const oidcLogin = async (provider: string, code: string, apiUrl?: string)
} }
}; };
let running_id: string | null = null;
let running: ReturnType<typeof getTokenWJ> | null = null; let running: ReturnType<typeof getTokenWJ> | null = null;
export const getTokenWJ = async (account?: Account | null): ReturnType<typeof run> => { export const getTokenWJ = async (
async function run() { acc?: Account | null,
if (account === undefined) account = getCurrentAccount(); forceRefresh: boolean = false,
if (!account) return [null, null, null] as const; ): Promise<readonly [string, Token, null] | readonly [null, null, KyooErrors | null]> => {
if (acc === undefined) acc = getCurrentAccount();
if (!acc) return [null, null, null] as const;
const account = acc;
async function run() {
let token = account.token; let token = account.token;
if (account.token.expire_at <= new Date(new Date().getTime() + 10 * 1000)) { if (forceRefresh || account.token.expire_at <= new Date(new Date().getTime() + 10 * 1000)) {
console.log("refreshing token for account", account.slug); console.log("refreshing token for account", account.slug);
try { try {
token = await queryFn( token = await queryFn(
@ -121,9 +126,11 @@ export const getTokenWJ = async (account?: Account | null): ReturnType<typeof ru
// Do not cache promise durring ssr. // Do not cache promise durring ssr.
if (Platform.OS === "web" && typeof window === "undefined") return await run(); if (Platform.OS === "web" && typeof window === "undefined") return await run();
if (running) return await running; if (running && running_id === account.id) return await running;
running_id = account.id;
running = run(); running = run();
const ret = await running; const ret = await running;
running_id = null;
running = null; running = null;
return ret; return ret;
}; };

View File

@ -28,7 +28,7 @@ import {
import { z } from "zod"; import { z } from "zod";
import { KyooErrors } from "./kyoo-errors"; import { KyooErrors } from "./kyoo-errors";
import { Page, Paged } from "./page"; import { Page, Paged } from "./page";
import { getToken } from "./login"; import { getToken, getTokenWJ } from "./login";
import { getCurrentApiUrl } from "."; import { getCurrentApiUrl } from ".";
export let lastUsedUrl: string = null!; export let lastUsedUrl: string = null!;
@ -48,12 +48,13 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
} & Partial<QueryFunctionContext>) } & Partial<QueryFunctionContext>)
), ),
type?: Parser, type?: Parser,
token?: string | null, iToken?: string | null,
): Promise<z.infer<Parser>> => { ): Promise<z.infer<Parser>> => {
const url = context.apiUrl && context.apiUrl.length > 0 ? context.apiUrl : getCurrentApiUrl(); const url = context.apiUrl && context.apiUrl.length > 0 ? context.apiUrl : getCurrentApiUrl();
lastUsedUrl = url!; lastUsedUrl = url!;
if (token === undefined && context.authenticated !== false) token = await getToken(); const token =
iToken === undefined && context.authenticated !== false ? await getToken() : undefined;
const path = [url] const path = [url]
.concat( .concat(
"path" in context "path" in context
@ -90,6 +91,14 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
if (resp.status === 404) { if (resp.status === 404) {
throw { errors: ["Resource not found."], status: 404 } as KyooErrors; throw { errors: ["Resource not found."], status: 404 } as KyooErrors;
} }
// If we got a forbidden, try to refresh the token
// if we got a token as an argument, it either means we already retried or we go one provided that's fresh
// so we can't retry either ways.
if (resp.status === 403 && iToken === undefined && token) {
const [newToken, _, error] = await getTokenWJ(undefined, true);
if (newToken) return await queryFn(context, type, newToken);
else console.error("refresh error while retrying a forbidden", error);
}
if (!resp.ok) { if (!resp.ok) {
const error = await resp.text(); const error = await resp.text();
let data; let data;
@ -128,7 +137,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
errors: [ errors: [
"Invalid response from kyoo. Possible version mismatch between the server and the application.", "Invalid response from kyoo. Possible version mismatch between the server and the application.",
], ],
status: "parse" status: "parse",
} as KyooErrors; } as KyooErrors;
} }
return parsed.data; return parsed.data;