import { AbsoluteTime, HttpStatusCode } from "@gnu-taler/taler-util"; import { ErrorType, HttpError, RequestError, useApiContext } from "@gnu-taler/web-util/browser"; import { getInitialBackendBaseURL } from "./backend.js"; export function useCredentialsChecker() { const { request } = useApiContext(); const baseUrl = getInitialBackendBaseURL(); //check against instance details endpoint //while merchant backend doesn't have a login endpoint async function requestNewLoginToken( username: string, password: string, ): Promise { const data: LoginTokenRequest = { scope: "readwrite" as "write", //FIX: different than merchant duration: { // d_us: "forever" //FIX: should return shortest d_us: 1000 * 60 * 60 * 23 }, refreshable: true, } try { const response = await request(baseUrl, `accounts/${username}/token`, { method: "POST", basicAuth: { username: username, password, }, data, contentType: "json" }); return { valid: true, token: response.data.token, expiration: response.data.expiration }; } catch (error) { if (error instanceof RequestError) { return { valid: false, cause: error.cause }; } return { valid: false, cause: { type: ErrorType.UNEXPECTED, loading: false, info: { hasToken: true, status: 0, options: {}, url: `/private/token`, payload: {} }, exception: error, message: (error instanceof Error ? error.message : "unpexepected error") } }; } }; async function refreshLoginToken( baseUrl: string, token: LoginToken ): Promise { if (AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(token.expiration))) { return { valid: false, cause: { type: ErrorType.CLIENT, status: HttpStatusCode.Unauthorized, message: "login token expired, login again.", info: { hasToken: true, status: 401, options: {}, url: `/private/token`, payload: {} }, payload: {} }, } } return requestNewLoginToken(baseUrl, token.token as AccessToken) } return { requestNewLoginToken, refreshLoginToken } } export interface LoginToken { token: string, expiration: Timestamp, } // token used to get loginToken // must forget after used declare const __ac_token: unique symbol; export type AccessToken = string & { [__ac_token]: true; }; type YesOrNo = "yes" | "no"; export type LoginResult = { valid: true; token: string; expiration: Timestamp; } | { valid: false; cause: HttpError<{}>; } // DELETE /private/instances/$INSTANCE export interface LoginTokenRequest { // Scope of the token (which kinds of operations it will allow) scope: "readonly" | "write"; // Server may impose its own upper bound // on the token validity duration duration?: RelativeTime; // Can this token be refreshed? // Defaults to false. refreshable?: boolean; } export interface LoginTokenSuccessResponse { // The login token that can be used to access resources // that are in scope for some time. Must be prefixed // with "Bearer " when used in the "Authorization" HTTP header. // Will already begin with the RFC 8959 prefix. token: string; // Scope of the token (which kinds of operations it will allow) scope: "readonly" | "write"; // Server may impose its own upper bound // on the token validity duration expiration: Timestamp; // Can this token be refreshed? refreshable: boolean; }