Skip to content

Commit

Permalink
Prevent attempting to authenticate more than four times per hour
Browse files Browse the repository at this point in the history
This only affects scripts - running this nxapi command in a terminal and the Electron app will ignore this limit.

#9
  • Loading branch information
samuelthomas2774 committed Jul 27, 2022
1 parent d3e5a88 commit e12bb36
Show file tree
Hide file tree
Showing 32 changed files with 109 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ nxapi exports it's API library and types. [See src/exports.](src/exports)
Example authenticating to the Nintendo Switch Online app:

> This is a simplified example of authenticating to the Coral API and using cached tokens. More logic is required to ensure you are using these APIs properly - [see src/common/auth/nso.ts for the authentication functions used in nxapi's CLI and Electron app](src/common/auth/nso.ts).
> This is a simplified example of authenticating to the Coral API and using cached tokens. More logic is required to ensure you are using these APIs properly - [see src/common/auth/coral.ts for the authentication functions used in nxapi's CLI and Electron app](src/common/auth/coral.ts).
```ts
import { addUserAgent } from 'nxapi';
Expand Down
2 changes: 1 addition & 1 deletion src/api/znc-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActiveEvent, Announcements, CurrentUser, Event, Friend, Presence, Prese
import { ErrorResponse } from './util.js';
import CoralApi from './coral.js';
import { NintendoAccountUser } from './na.js';
import { SavedToken } from '../common/auth/nso.js';
import { SavedToken } from '../common/auth/coral.js';
import { timeoutSignal } from '../util/misc.js';
import { getAdditionalUserAgents, getUserAgent } from '../util/useragent.js';

Expand Down
2 changes: 1 addition & 1 deletion src/app/browser/util.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ErrorResponse } from '../../api/util.js';
import { DiscordPresence } from '../../discord/util.js';
import ipc, { events } from './ipc.js';
import { NintendoAccountUser } from '../../api/na.js';
import { SavedToken } from '../../common/auth/nso.js';
import { SavedToken } from '../../common/auth/coral.js';
import { SavedMoonToken } from '../../common/auth/moon.js';
import { BACKGROUND_COLOUR_MAIN_DARK, BACKGROUND_COLOUR_MAIN_LIGHT, DEFAULT_ACCENT_COLOUR } from './constants.js';

Expand Down
3 changes: 2 additions & 1 deletion src/app/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ export class Store extends EventEmitter {
) {
super();

this.users = Users.coral(storage, process.env.ZNC_PROXY_URL);
// ratelimit = false, as most users.get calls are triggered by user interaction (or at startup)
this.users = Users.coral(storage, process.env.ZNC_PROXY_URL, false);
}

async saveMonitorState(monitors: PresenceMonitorManager) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js';
import { App } from './index.js';
import { WebService } from '../../api/coral-types.js';
import openWebService from './webservices.js';
import { SavedToken } from '../../common/auth/nso.js';
import { SavedToken } from '../../common/auth/coral.js';
import { SavedMoonToken } from '../../common/auth/moon.js';
import { dev, dir } from '../../util/product.js';
import { EmbeddedPresenceMonitor, EmbeddedProxyPresenceMonitor } from './monitor.js';
Expand Down
2 changes: 1 addition & 1 deletion src/app/main/na-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BrowserWindow, dialog, MessageBoxOptions, Notification, session, shell
import { getNintendoAccountSessionToken, NintendoAccountSessionToken } from '../../api/na.js';
import { ZNCA_CLIENT_ID } from '../../api/coral.js';
import { ZNMA_CLIENT_ID } from '../../api/moon.js';
import { getToken, SavedToken } from '../../common/auth/nso.js';
import { getToken, SavedToken } from '../../common/auth/coral.js';
import { getPctlToken, SavedMoonToken } from '../../common/auth/moon.js';
import { Jwt } from '../../util/jwt.js';
import { tryGetNativeImageFromUrl } from './util.js';
Expand Down
2 changes: 1 addition & 1 deletion src/app/main/webservices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { dev } from '../../util/product.js';
import { WebService } from '../../api/coral-types.js';
import { Store } from './index.js';
import type { NativeShareRequest, NativeShareUrlRequest } from '../preload-webservice/znca-js-api.js';
import { SavedToken } from '../../common/auth/nso.js';
import { SavedToken } from '../../common/auth/coral.js';
import { createWebServiceWindow } from './windows.js';

const debug = createDebug('app:main:webservices');
Expand Down
2 changes: 1 addition & 1 deletion src/app/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import createDebug from 'debug';
import type { User } from 'discord-rpc';
import type { SharingItem } from '../main/electron.js';
import type { DiscordPresenceConfiguration, DiscordPresenceSource, WindowConfiguration } from '../common/types.js';
import type { SavedToken } from '../../common/auth/nso.js';
import type { SavedToken } from '../../common/auth/coral.js';
import type { SavedMoonToken } from '../../common/auth/moon.js';
import type { UpdateCacheData } from '../../common/update.js';
import type { Announcements, CurrentUser, Friend, GetActiveEventResult, WebService, WebServices } from '../../api/coral-types.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/add-friend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:add-friend');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/announcements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:announcements');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import { getNintendoAccountSessionToken } from '../../api/na.js';
import { ZNCA_CLIENT_ID } from '../../api/coral.js';
import prompt from '../util/prompt.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/friendcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:friendcode');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/friends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { hrduration } from '../../util/misc.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:friends');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import CoralApi from '../../api/coral.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken, SavedToken } from '../../common/auth/nso.js';
import { getToken, SavedToken } from '../../common/auth/coral.js';
import { NotificationManager, PresenceEvent, ZncNotifications } from '../../common/notify.js';
import { product } from '../../util/product.js';
import { parseListenAddress } from '../../util/net.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:lookup');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import persist from 'node-persist';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import { getIksmToken } from '../../common/auth/splatnet2.js';
import { EmbeddedSplatNet2Monitor, NotificationManager, ZncNotifications } from '../../common/notify.js';
import { CurrentUser, Friend, Game } from '../../api/coral-types.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PresencePermissions } from '../../api/coral-types.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:permissions');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import { DiscordPresencePlayTime } from '../../discord/util.js';
import { handleEnableSplatNet2Monitoring, TerminalNotificationManager } from './notify.js';
import { ZncDiscordPresence, ZncProxyDiscordPresence } from '../../common/presence.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import prompt from '../util/prompt.js';

const debug = createDebug('cli:nso:token');
Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:user');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/webservices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:webservices');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/webservicetoken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';

const debug = createDebug('cli:nso:webservicetoken');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/nso/znc-proxy-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import createDebug from 'debug';
import fetch from 'node-fetch';
import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import { AuthPolicy, AuthToken } from '../../api/znc-proxy.js';
import { Argv } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Table from './util/table.js';
import type { Arguments as ParentArguments } from '../cli.js';
import { Argv } from '../util/yargs.js';
import { initStorage } from '../util/storage.js';
import { SavedToken } from '../common/auth/nso.js';
import { SavedToken } from '../common/auth/coral.js';
import { SavedMoonToken } from '../common/auth/moon.js';

const debug = createDebug('cli:users');
Expand Down
2 changes: 1 addition & 1 deletion src/cli/util/discord-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Arguments as ParentArguments } from '../util.js';
import { DiscordPresenceContext, DiscordPresencePlayTime, getDiscordPresence, getInactiveDiscordPresence } from '../../discord/util.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getToken } from '../../common/auth/nso.js';
import { getToken } from '../../common/auth/coral.js';
import { timeoutSignal } from '../../util/misc.js';
import { getUserAgent } from '../../util/useragent.js';

Expand Down
15 changes: 12 additions & 3 deletions src/common/auth/nso.ts → src/common/auth/coral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Jwt } from '../../util/jwt.js';
import { AccountLogin, CoralErrorResponse } from '../../api/coral-types.js';
import CoralApi, { ZNCA_CLIENT_ID } from '../../api/coral.js';
import ZncProxyApi from '../../api/znc-proxy.js';
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';

const debug = createDebug('nxapi:auth:nso');

Expand All @@ -24,15 +25,21 @@ export interface SavedToken {
proxy_url?: string;
}

export async function getToken(storage: persist.LocalStorage, token: string, proxy_url: string): Promise<{
export async function getToken(
storage: persist.LocalStorage, token: string, proxy_url: string, ratelimit?: boolean
): Promise<{
nso: ZncProxyApi;
data: SavedToken;
}>
export async function getToken(storage: persist.LocalStorage, token: string, proxy_url?: string): Promise<{
export async function getToken(
storage: persist.LocalStorage, token: string, proxy_url?: string, ratelimit?: boolean
): Promise<{
nso: CoralApi;
data: SavedToken;
}>
export async function getToken(storage: persist.LocalStorage, token: string, proxy_url?: string) {
export async function getToken(
storage: persist.LocalStorage, token: string, proxy_url?: string, ratelimit = SHOULD_LIMIT_USE
) {
if (!token) {
console.error('No token set. Set a Nintendo Account session token using the `--token` option or by running `nxapi nso token`.');
throw new Error('Invalid token');
Expand All @@ -58,6 +65,8 @@ export async function getToken(storage: persist.LocalStorage, token: string, pro
const existingToken: SavedToken | undefined = await storage.getItem('NsoToken.' + token);

if (!existingToken || existingToken.expires_at <= Date.now()) {
if (ratelimit) await checkUseLimit(storage, 'coral', jwt.payload.sub);

console.warn('Authenticating to Nintendo Switch Online app');
debug('Authenticating to znc with session token');

Expand Down
5 changes: 4 additions & 1 deletion src/common/auth/moon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ZNMA_CLIENT_ID } from '../../api/moon.js';
import { NintendoAccountSessionTokenJwtPayload, NintendoAccountToken, NintendoAccountUser } from '../../api/na.js';
import { Jwt } from '../../util/jwt.js';
import MoonApi from '../../api/moon.js';
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';

const debug = createDebug('nxapi:auth:moon');

Expand All @@ -14,7 +15,7 @@ export interface SavedMoonToken {
expires_at: number;
}

export async function getPctlToken(storage: persist.LocalStorage, token: string) {
export async function getPctlToken(storage: persist.LocalStorage, token: string, ratelimit = SHOULD_LIMIT_USE) {
if (!token) {
console.error('No token set. Set a Nintendo Account session token using the `--token` option or by running `nxapi pctl auth`.');
throw new Error('Invalid token');
Expand All @@ -40,6 +41,8 @@ export async function getPctlToken(storage: persist.LocalStorage, token: string)
const existingToken: SavedMoonToken | undefined = await storage.getItem('MoonToken.' + token);

if (!existingToken || existingToken.expires_at <= Date.now()) {
if (ratelimit) await checkUseLimit(storage, 'moon', jwt.payload.sub);

console.warn('Authenticating to Nintendo Switch Parental Controls app');
debug('Authenticating to pctl with session token');

Expand Down
22 changes: 18 additions & 4 deletions src/common/auth/nooklink.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import createDebug from 'debug';
import persist from 'node-persist';
import { getToken } from './nso.js';
import { getToken } from './coral.js';
import NooklinkApi, { NooklinkUserApi } from '../../api/nooklink.js';
import { AuthToken, Users } from '../../api/nooklink-types.js';
import { WebServiceToken } from '../../api/coral-types.js';
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';
import { Jwt } from '../../util/jwt.js';
import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js';

const debug = createDebug('nxapi:auth:nooklink');

Expand All @@ -19,7 +22,8 @@ export interface SavedToken {
}

export async function getWebServiceToken(
storage: persist.LocalStorage, token: string, proxy_url?: string, allow_fetch_token = false
storage: persist.LocalStorage, token: string, proxy_url?: string,
allow_fetch_token = false, ratelimit = SHOULD_LIMIT_USE
) {
if (!token) {
console.error('No token set. Set a Nintendo Account session token using the `--token` option or by running `nxapi nso token`.');
Expand All @@ -30,7 +34,12 @@ export async function getWebServiceToken(

if (!existingToken || existingToken.expires_at <= Date.now()) {
if (!allow_fetch_token) {
throw new Error('No valid _gtoken cookie');
throw new Error('No valid NookLink web service token');
}

if (ratelimit) {
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
await checkUseLimit(storage, 'nooklink', jwt.payload.sub);
}

console.warn('Authenticating to NookLink');
Expand Down Expand Up @@ -66,7 +75,7 @@ type PromiseValue<T> = T extends PromiseLike<infer R> ? R : never;

export async function getUserToken(
storage: persist.LocalStorage, nintendoAccountToken: string, user?: string,
proxy_url?: string, allow_fetch_token = false
proxy_url?: string, allow_fetch_token = false, ratelimit = SHOULD_LIMIT_USE
) {
let wst: PromiseValue<ReturnType<typeof getWebServiceToken>> | null = null;

Expand Down Expand Up @@ -106,6 +115,11 @@ export async function getUserToken(
if (!wst) wst = await getWebServiceToken(storage, nintendoAccountToken, proxy_url, allow_fetch_token);
const {nooklink, data: webserviceToken} = wst;

if (ratelimit) {
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(nintendoAccountToken);
await checkUseLimit(storage, 'nooklink-user', jwt.payload.sub);
}

console.warn('Authenticating to NookLink as user %s', user);
debug('Authenticating to NookLink as user %s', user);

Expand Down
15 changes: 13 additions & 2 deletions src/common/auth/splatnet2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import process from 'node:process';
import * as fs from 'node:fs';
import createDebug from 'debug';
import persist from 'node-persist';
import { getToken } from './nso.js';
import { getToken } from './coral.js';
import SplatNet2Api, { updateIksmSessionLastUsed } from '../../api/splatnet2.js';
import { WebServiceToken } from '../../api/coral-types.js';
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';
import { Jwt } from '../../util/jwt.js';
import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js';

const debug = createDebug('nxapi:auth:splatnet2');

Expand All @@ -27,7 +30,10 @@ export interface SavedIksmSessionToken {
last_used?: number;
}

export async function getIksmToken(storage: persist.LocalStorage, token: string, proxy_url?: string, allow_fetch_token = false) {
export async function getIksmToken(
storage: persist.LocalStorage, token: string, proxy_url?: string,
allow_fetch_token = false, ratelimit = SHOULD_LIMIT_USE
) {
if (!token) {
console.error('No token set. Set a Nintendo Account session token using the `--token` option or by running `nxapi nso token`.');
throw new Error('Invalid token');
Expand All @@ -44,6 +50,11 @@ export async function getIksmToken(storage: persist.LocalStorage, token: string,
throw new Error('No valid iksm_session cookie');
}

if (ratelimit) {
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
await checkUseLimit(storage, 'splatnet2', jwt.payload.sub);
}

console.warn('Authenticating to SplatNet 2');
debug('Authenticating to SplatNet 2');

Expand Down

0 comments on commit e12bb36

Please sign in to comment.