mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-09-10 12:25:09 -05:00
Handle errors from the Nintendo Account authorisation page
This commit is contained in:
@@ -6,7 +6,7 @@ import openWebService, { WebServiceIpc } from './webservices.js';
|
||||
import { createWindow, getWindowConfiguration } from './windows.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceSource, WindowType } from '../common/types.js';
|
||||
import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js';
|
||||
import { addNsoAccount, addPctlAccount } from './na-auth.js';
|
||||
import { addNsoAccount, addPctlAccount, askAddNsoAccount, askAddPctlAccount, AuthoriseError } from './na-auth.js';
|
||||
import { App } from './index.js';
|
||||
import { NintendoAccountUser } from '../../api/na.js';
|
||||
import { hrduration } from '../../util/misc.js';
|
||||
@@ -45,8 +45,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
ipcMain.handle('nxapi:accounts:list', () => storage.getItem('NintendoAccountIds'));
|
||||
ipcMain.handle('nxapi:accounts:add-coral', () => addNsoAccount(store.storage).then(u => u.data.user.id));
|
||||
ipcMain.handle('nxapi:accounts:add-moon', () => addPctlAccount(store.storage).then(u => u.data.user.id));
|
||||
ipcMain.handle('nxapi:accounts:add-coral', () => askAddNsoAccount(store.storage).then(u => u?.data.user.id));
|
||||
ipcMain.handle('nxapi:accounts:add-moon', () => askAddPctlAccount(store.storage).then(u => u?.data.user.id));
|
||||
|
||||
ipcMain.handle('nxapi:nso:gettoken', (e, id: string) => storage.getItem('NintendoAccountToken.' + id));
|
||||
ipcMain.handle('nxapi:nso:getcachedtoken', (e, token: string) => storage.getItem('NsoToken.' + token));
|
||||
@@ -147,8 +147,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
new MenuItem({label: 'Use the nxapi command to remove this user', enabled: false}),
|
||||
]).popup({window: BrowserWindow.fromWebContents(e.sender)!}), undefined));
|
||||
ipcMain.handle('nxapi:menu:add-user', e => (Menu.buildFromTemplate([
|
||||
new MenuItem({label: 'Add Nintendo Switch Online account', click: () => addNsoAccount(storage)}),
|
||||
new MenuItem({label: 'Add Nintendo Switch Parental Controls account', click: () => addPctlAccount(storage)}),
|
||||
new MenuItem({label: 'Add Nintendo Switch Online account', click: () => askAddNsoAccount(storage)}),
|
||||
new MenuItem({label: 'Add Nintendo Switch Parental Controls account', click: () => askAddPctlAccount(storage)}),
|
||||
]).popup({window: BrowserWindow.fromWebContents(e.sender)!}), undefined));
|
||||
ipcMain.handle('nxapi:menu:friend-code', (e, fc: CurrentUser['links']['friendCode']) => (Menu.buildFromTemplate([
|
||||
new MenuItem({label: 'SW-' + fc.id, enabled: false}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { app, dialog, Menu, Tray, nativeImage, MenuItem } from './electron.js';
|
||||
import createDebug from 'debug';
|
||||
import { addNsoAccount, addPctlAccount } from './na-auth.js';
|
||||
import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js';
|
||||
import { App } from './index.js';
|
||||
import { WebService } from '../../api/coral-types.js';
|
||||
import openWebService from './webservices.js';
|
||||
@@ -105,21 +105,8 @@ export default class MenuApp {
|
||||
this.tray.setContextMenu(menu);
|
||||
}
|
||||
|
||||
addNsoAccount = () => {
|
||||
addNsoAccount(this.app.store.storage).catch(err => {
|
||||
if (err.message === 'Canceled') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
});
|
||||
};
|
||||
|
||||
addPctlAccount = () => {
|
||||
addPctlAccount(this.app.store.storage).catch(err => {
|
||||
if (err.message === 'Canceled') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
});
|
||||
};
|
||||
addNsoAccount = () => askAddNsoAccount(this.app.store.storage);
|
||||
addPctlAccount = () => askAddPctlAccount(this.app.store.storage);
|
||||
|
||||
// Hardcode these temporarily until they are cached
|
||||
webservices: WebService[] | null = [
|
||||
|
||||
@@ -80,6 +80,23 @@ export interface NintendoAccountSessionTokenCode {
|
||||
window?: BrowserWindow;
|
||||
}
|
||||
|
||||
export class AuthoriseError extends Error {
|
||||
constructor(readonly code: string, message?: string) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
static fromSearchParams(qs: URLSearchParams) {
|
||||
const code = qs.get('error') ?? 'unknown_error';
|
||||
return new AuthoriseError(code, qs.get('error_description') ?? code);
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthoriseCancelError extends AuthoriseError {
|
||||
constructor(message?: string) {
|
||||
super('access_denied', message);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSessionTokenCode(client_id: string, scope: string | string[], close_window: false):
|
||||
Promise<NintendoAccountSessionTokenCode & {window: BrowserWindow}>
|
||||
export function getSessionTokenCode(client_id: string, scope: string | string[], close_window: true):
|
||||
@@ -91,34 +108,57 @@ export function getSessionTokenCode(client_id: string, scope: string | string[],
|
||||
const {url: authoriseurl, state, verifier, challenge} = getAuthUrl(client_id, scope);
|
||||
const window = createAuthWindow();
|
||||
|
||||
const handleAuthUrl = (url: URL) => {
|
||||
const authorisedparams = new URLSearchParams(url.hash.substr(1));
|
||||
debug('Redirect URL parameters', [...authorisedparams.entries()]);
|
||||
|
||||
if (authorisedparams.get('state') !== state) {
|
||||
rj(new Error('Invalid state'));
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (authorisedparams.has('error')) {
|
||||
rj(AuthoriseError.fromSearchParams(authorisedparams));
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authorisedparams.has('session_token_code')) {
|
||||
rj(new Error('Response didn\'t include a session token code'));
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const code = authorisedparams.get('session_token_code')!;
|
||||
const [jwt, sig] = Jwt.decode(code);
|
||||
|
||||
debug('code', code, jwt, sig);
|
||||
|
||||
if (close_window) {
|
||||
rs({
|
||||
code,
|
||||
verifier,
|
||||
});
|
||||
|
||||
window.close();
|
||||
} else {
|
||||
rs({
|
||||
code,
|
||||
verifier,
|
||||
window,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.webContents.on('will-navigate', (event, url_string) => {
|
||||
const url = new URL(url_string);
|
||||
|
||||
debug('will navigate', url);
|
||||
|
||||
if (url.protocol === 'npf' + client_id + ':' && url.host === 'auth') {
|
||||
const authorisedparams = new URLSearchParams(url.hash.substr(1));
|
||||
debug('Redirect URL parameters', [...authorisedparams.entries()]);
|
||||
|
||||
const code = authorisedparams.get('session_token_code')!;
|
||||
const [jwt, sig] = Jwt.decode(code);
|
||||
|
||||
debug('code', code, jwt, sig);
|
||||
|
||||
if (close_window) {
|
||||
rs({
|
||||
code,
|
||||
verifier,
|
||||
});
|
||||
|
||||
window.close();
|
||||
} else {
|
||||
rs({
|
||||
code,
|
||||
verifier,
|
||||
window,
|
||||
});
|
||||
}
|
||||
handleAuthUrl(url);
|
||||
event.preventDefault();
|
||||
} else if (url.origin === 'https://accounts.nintendo.com') {
|
||||
// Ok
|
||||
} else {
|
||||
@@ -127,7 +167,7 @@ export function getSessionTokenCode(client_id: string, scope: string | string[],
|
||||
});
|
||||
|
||||
window.on('closed', () => {
|
||||
rj(new Error('Canceled'));
|
||||
rj(new AuthoriseCancelError('Canceled'));
|
||||
});
|
||||
|
||||
window.webContents.on('did-fail-load', e => rj(e));
|
||||
@@ -135,8 +175,16 @@ export function getSessionTokenCode(client_id: string, scope: string | string[],
|
||||
window.webContents.userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Mobile/15E148 Safari/604.1';
|
||||
|
||||
window.webContents.setWindowOpenHandler(details => {
|
||||
const url = new URL(details.url);
|
||||
|
||||
debug('open', details);
|
||||
shell.openExternal(details.url);
|
||||
|
||||
if (url.protocol === 'npf' + client_id + ':' && url.host === 'auth') {
|
||||
handleAuthUrl(url);
|
||||
} else {
|
||||
shell.openExternal(details.url);
|
||||
}
|
||||
|
||||
return {action: 'deny'};
|
||||
});
|
||||
|
||||
@@ -208,6 +256,16 @@ export async function addNsoAccount(storage: persist.LocalStorage) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function askAddNsoAccount(storage: persist.LocalStorage) {
|
||||
try {
|
||||
return await addNsoAccount(storage);
|
||||
} catch (err: any) {
|
||||
if (err instanceof AuthoriseError && err.code === 'access_denied') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkZncaApiUseAllowed(storage: persist.LocalStorage, window?: BrowserWindow, force = false) {
|
||||
if (!force) {
|
||||
if (await storage.getItem('ZncaApiConsent')) {
|
||||
@@ -330,3 +388,13 @@ export async function addPctlAccount(storage: persist.LocalStorage) {
|
||||
window?.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function askAddPctlAccount(storage: persist.LocalStorage) {
|
||||
try {
|
||||
return await addPctlAccount(storage);
|
||||
} catch (err: any) {
|
||||
if (err instanceof AuthoriseError && err.code === 'access_denied') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user