Prevent using expired tokens

This commit is contained in:
Samuel Elliott
2023-05-25 14:04:17 +01:00
parent f6aa9a17ab
commit 512a9a2b60
11 changed files with 95 additions and 17 deletions

View File

@@ -41,9 +41,11 @@ export interface ResultData<T> {
}
export default class CoralApi {
onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise<CoralAuthData | void>) | null = null;
onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise<CoralAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
/** @internal */
_token_expired = false;
protected constructor(
public token: string,
@@ -57,8 +59,18 @@ export default class CoralApi {
async fetch<T = unknown>(
url: string, method = 'GET', body?: string, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<Result<T>> {
if (this._token_expired && _autoRenewToken && !this._renewToken) {
if (!this.onTokenExpired || _attempt) throw new Error('Token expired');
this._renewToken = this.onTokenExpired.call(null).then(data => {
if (data) this.setTokenWithSavedToken(data);
}).finally(() => {
this._renewToken = null;
});
}
if (this._renewToken && _autoRenewToken) {
await this._renewToken;
}
@@ -86,6 +98,7 @@ export default class CoralApi {
const data = await response.json() as CoralResponse<T>;
if (data.status === CoralStatus.TOKEN_EXPIRED && _autoRenewToken && !_attempt && this.onTokenExpired) {
this._token_expired = true;
// _renewToken will be awaited when calling fetch
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).then(data => {
if (data) this.setTokenWithSavedToken(data);
@@ -286,6 +299,7 @@ export default class CoralApi {
this.token = data.credential.accessToken;
this.coral_user_id = '' + data.nsoAccount.user.id;
if ('user' in data) this.na_id = data.user.id;
this._token_expired = false;
}
static async createWithSessionToken(token: string, useragent = getAdditionalUserAgents()) {

View File

@@ -16,9 +16,10 @@ const ZNMA_USER_AGENT = 'moon_ANDROID/' + ZNMA_VERSION + ' (com.nintendo.znma; b
'; ANDROID 26)';
export default class MoonApi {
onTokenExpired: ((data: MoonError, res: Response) => Promise<MoonAuthData | PartialMoonAuthData | void>) | null = null;
onTokenExpired: ((data?: MoonError, res?: Response) => Promise<MoonAuthData | PartialMoonAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public token: string,
@@ -31,8 +32,18 @@ export default class MoonApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
if (this._token_expired && _autoRenewToken && !this._renewToken) {
if (!this.onTokenExpired || _attempt) throw new Error('Token expired');
this._renewToken = this.onTokenExpired.call(null).then(data => {
if (data) this.setTokenWithSavedToken(data);
}).finally(() => {
this._renewToken = null;
});
}
if (this._renewToken && _autoRenewToken) {
await this._renewToken;
}
@@ -62,6 +73,7 @@ export default class MoonApi {
debug('fetch %s %s, response %s', method, url, response.status);
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
this._token_expired = true;
const data = await response.json() as MoonError;
// _renewToken will be awaited when calling fetch
@@ -123,6 +135,7 @@ export default class MoonApi {
private setTokenWithSavedToken(data: MoonAuthData | PartialMoonAuthData) {
this.token = data.nintendoAccountToken.access_token!;
if ('user' in data) this.naId = data.user.id;
this._token_expired = false;
}
static async createWithSessionToken(token: string) {

View File

@@ -17,9 +17,10 @@ const NOOKLINK_URL = NOOKLINK_WEBSERVICE_URL + '/api';
const BLANCO_VERSION = '2.1.1';
export default class NooklinkApi {
onTokenExpired: ((data: WebServiceError, res: Response) => Promise<NooklinkAuthData | void>) | null = null;
onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise<NooklinkAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public gtoken: string,
@@ -30,8 +31,18 @@ export default class NooklinkApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
if (this._token_expired && _autoRenewToken && !this._renewToken) {
if (!this.onTokenExpired || _attempt) throw new Error('Token expired');
this._renewToken = this.onTokenExpired.call(null).then(data => {
if (data) this.setTokenWithSavedToken(data);
}).finally(() => {
this._renewToken = null;
});
}
if (this._renewToken && _autoRenewToken) {
await this._renewToken;
}
@@ -57,6 +68,7 @@ export default class NooklinkApi {
debug('fetch %s %s, response %s', method, url, response.status);
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
this._token_expired = true;
const data = await response.json() as WebServiceError;
// _renewToken will be awaited when calling fetch
@@ -109,6 +121,7 @@ export default class NooklinkApi {
private setTokenWithSavedToken(data: NooklinkAuthData) {
this.gtoken = data.gtoken;
this._token_expired = false;
}
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {
@@ -195,9 +208,10 @@ export default class NooklinkApi {
}
export class NooklinkUserApi {
onTokenExpired: ((data: WebServiceError, res: Response) => Promise<NooklinkUserAuthData | PartialNooklinkUserAuthData | void>) | null = null;
onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise<NooklinkUserAuthData | PartialNooklinkUserAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public user_id: string,
@@ -211,8 +225,18 @@ export class NooklinkUserApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
if (this._token_expired && _autoRenewToken && !this._renewToken) {
if (!this.onTokenExpired || _attempt) throw new Error('Token expired');
this._renewToken = this.onTokenExpired.call(null).then(data => {
if (data) this.setTokenWithSavedToken(data);
}).finally(() => {
this._renewToken = null;
});
}
if (this._renewToken && _autoRenewToken) {
await this._renewToken;
}
@@ -239,6 +263,7 @@ export class NooklinkUserApi {
debug('fetch %s %s, response %s', method, url, response.status);
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
this._token_expired = true;
const data = await response.json() as WebServiceError;
// _renewToken will be awaited when calling fetch
@@ -327,6 +352,7 @@ export class NooklinkUserApi {
this.user_id = data.user_id;
this.auth_token = data.token.token;
this.gtoken = data.gtoken;
this._token_expired = false;
}
/** @internal */

View File

@@ -25,6 +25,8 @@ export const updateIksmSessionLastUsed: {
} = {};
export default class SplatNet2Api {
protected _session_expired = false;
protected constructor(
public iksm_session: string,
public unique_id: string,
@@ -32,6 +34,10 @@ export default class SplatNet2Api {
) {}
async fetch<T extends object>(url: string, method = 'GET', body?: string | FormData, headers?: object) {
if (this._session_expired) {
throw new Error('Session expired');
}
const [signal, cancel] = timeoutSignal();
const response = await fetch(SPLATNET2_URL + url, {
method,
@@ -52,6 +58,10 @@ export default class SplatNet2Api {
debug('fetch %s %s, response %s', method, url, response.status);
if (response.status === 401) {
this._session_expired = true;
}
if (response.status !== 200) {
throw new ErrorResponse('[splatnet2] Non-200 status code', response, await response.text());
}

View File

@@ -74,9 +74,10 @@ enum MapQueriesMode {
export default class SplatNet3Api {
onTokenShouldRenew: ((remaining: number, res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res?: Response) => Promise<SplatNet3AuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
graphql_strict = process.env.NXAPI_SPLATNET3_STRICT !== '0';
@@ -93,8 +94,18 @@ export default class SplatNet3Api {
async fetch<T = unknown>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _log?: string,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
if (this._token_expired && !this._renewToken) {
if (!this.onTokenExpired || _attempt) throw new Error('Token expired');
this._renewToken = this.onTokenExpired.call(null).then(data => {
if (data) this.setTokenWithSavedToken(data);
}).finally(() => {
this._renewToken = null;
});
}
if (this._renewToken) {
await this._renewToken;
}
@@ -121,6 +132,7 @@ export default class SplatNet3Api {
response.status, version);
if (response.status === 401 && !_attempt && this.onTokenExpired) {
this._token_expired = true;
// _renewToken will be awaited when calling fetch
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, response).then(data => {
if (data) this.setTokenWithSavedToken(data);
@@ -933,6 +945,7 @@ export default class SplatNet3Api {
this.version = data.version;
this.language = data.bullet_token.lang;
this.useragent = data.useragent;
this._token_expired = false;
}
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {

View File

@@ -12,10 +12,12 @@ const debug = createDebug('nxapi:api:znc-proxy');
export default class ZncProxyApi implements CoralApi {
// Not used by ZncProxyApi
onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise<CoralAuthData | void>) | null = null;
onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise<CoralAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
/** @internal */
_token_expired = false;
/** @internal */
na_id = '';
/** @internal */

View File

@@ -148,7 +148,7 @@ function createTokenExpiredHandler(
data: {coral: Coral; auth_data: SavedToken; znc_proxy_url?: string},
ratelimit = true
) {
return (response: Response) => {
return (response?: Response) => {
debug('Token expired, renewing');
return renewToken(session, splatnet, data, ratelimit);
};

View File

@@ -99,7 +99,7 @@ function createTokenExpiredHandler(
storage: persist.LocalStorage, token: string, nso: CoralApi,
renew_token_data: {existingToken: SavedToken}, ratelimit = true
) {
return (data: CoralErrorResponse, response: Response) => {
return (data?: CoralErrorResponse, response?: Response) => {
debug('Token expired', renew_token_data.existingToken.user.id, data);
return renewToken(storage, token, nso, renew_token_data, ratelimit);
};

View File

@@ -76,7 +76,7 @@ function createTokenExpiredHandler(
storage: persist.LocalStorage, token: string, moon: MoonApi,
renew_token_data: {existingToken: SavedMoonToken}, ratelimit = true
) {
return (data: MoonError, response: Response) => {
return (data?: MoonError, response?: Response) => {
debug('Token expired', renew_token_data.existingToken.user.id, data);
return renewToken(storage, token, moon, renew_token_data, ratelimit);
};

View File

@@ -79,7 +79,7 @@ function createTokenExpiredHandler(
renew_token_data: {existingToken: SavedToken; znc_proxy_url?: string},
ratelimit = true
) {
return (data: WebServiceError, response: Response) => {
return (data?: WebServiceError, response?: Response) => {
debug('Token expired, renewing', data);
return renewToken(storage, token, nooklink, renew_token_data, ratelimit);
};
@@ -196,7 +196,7 @@ function createUserTokenExpiredHandler(
renew_token_data: {existingToken: SavedUserToken; znc_proxy_url?: string; nooklink: NooklinkApi | null},
ratelimit = true
) {
return (data: WebServiceError, response: Response) => {
return (data?: WebServiceError, response?: Response) => {
debug('Token expired', nooklinkuser.user_id, data);
return renewUserToken(storage, token, nooklinkuser, renew_token_data);
};

View File

@@ -79,7 +79,7 @@ function createTokenExpiredHandler(
data: {existingToken: SavedBulletToken; znc_proxy_url?: string},
ratelimit = true
) {
return (response: Response) => {
return (response?: Response) => {
debug('Token expired, renewing');
return renewToken(storage, token, splatnet, data, ratelimit);
};