mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-09-14 14:26:48 -05:00
Add API proxy access tokens
This allows providing access to the API proxy server as your account but only to access certain data (probably user/friend presence for Discord). For example a token generated using `nxapi nso znc-proxy-tokens --znc-proxy-url "http://[::1]:12345/api/znc" create --policy-user-presence` will only be able to access your presence at http://[::1]:12345/api/znc/user/presence (with the access token provided in the Authorization header as a Bearer token or in the token query string parameter).
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import createDebug from 'debug';
|
||||
import express from 'express';
|
||||
import express, { Request, Response } from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import * as net from 'net';
|
||||
import persist from 'node-persist';
|
||||
import { v4 as uuidgen } from 'uuid';
|
||||
import { ActiveEvent, Announcement, CurrentUser, Friend, Presence, WebService } from '../../api/znc-types.js';
|
||||
import ZncApi from '../../api/znc.js';
|
||||
import type { Arguments as ParentArguments } from '../nso.js';
|
||||
@@ -13,10 +15,34 @@ declare global {
|
||||
interface Request {
|
||||
znc?: ZncApi;
|
||||
zncAuth?: SavedToken;
|
||||
|
||||
zncAuthPolicy?: AuthPolicy;
|
||||
zncAuthPolicyUser?: string;
|
||||
zncAuthPolicyToken?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthToken {
|
||||
user: string;
|
||||
policy?: AuthPolicy;
|
||||
created_at: number;
|
||||
}
|
||||
export interface AuthPolicy {
|
||||
announcements?: boolean;
|
||||
list_friends?: boolean;
|
||||
list_friends_presence?: boolean;
|
||||
friend?: boolean;
|
||||
friend_presence?: boolean;
|
||||
webservices?: boolean;
|
||||
activeevent?: boolean;
|
||||
current_user?: boolean;
|
||||
current_user_presence?: boolean;
|
||||
|
||||
friends?: string[];
|
||||
friends_presence?: string[];
|
||||
}
|
||||
|
||||
const debug = createDebug('cli:nso:http-server');
|
||||
|
||||
export const command = 'http-server';
|
||||
@@ -68,11 +94,47 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
next();
|
||||
};
|
||||
|
||||
const authToken: express.RequestHandler = async (req, res, next) => {
|
||||
if (req.headers['authorization']?.startsWith('Bearer ')) {
|
||||
const token = req.headers['authorization'].substr(7);
|
||||
|
||||
const auth: AuthToken | undefined = await storage.getItem('ZncProxyAuthPolicy.' + token);
|
||||
if (!auth) return next();
|
||||
|
||||
req.zncAuthPolicy = auth.policy;
|
||||
req.zncAuthPolicyUser = auth.user;
|
||||
req.zncAuthPolicyToken = token;
|
||||
} else if (req.query.token) {
|
||||
const auth: AuthToken | undefined = await storage.getItem('ZncProxyAuthPolicy.' + req.query.token);
|
||||
if (!auth) return next();
|
||||
|
||||
req.zncAuthPolicy = auth.policy;
|
||||
req.zncAuthPolicyUser = auth.user;
|
||||
req.zncAuthPolicyToken = '' + req.query.token;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
function tokenUnauthorised(req: Request, res: Response) {
|
||||
res.statusCode = 403;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({
|
||||
error: 'token_unauthorised',
|
||||
}));
|
||||
}
|
||||
|
||||
const nsoAuth: express.RequestHandler = async (req, res, next) => {
|
||||
try {
|
||||
const auth = req.headers['authorization'];
|
||||
if (!auth || !auth.startsWith('na ')) throw new Error('Requires Nintendo Account authentication');
|
||||
const nintendoAccountSessionToken = auth.substr(3);
|
||||
let nintendoAccountSessionToken: string;
|
||||
if (req.zncAuthPolicyUser) {
|
||||
const na_token = await storage.getItem('NintendoAccountToken.' + req.zncAuthPolicyUser);
|
||||
if (!na_token) throw new Error('Nintendo Account for this token must reauthenticate');
|
||||
nintendoAccountSessionToken = na_token;
|
||||
} else {
|
||||
const auth = req.headers['authorization'];
|
||||
if (!auth || !auth.startsWith('na ')) throw new Error('Requires Nintendo Account authentication');
|
||||
nintendoAccountSessionToken = auth.substr(3);
|
||||
}
|
||||
|
||||
const {nso, data} = await getToken(storage, nintendoAccountSessionToken, argv.zncProxyUrl);
|
||||
|
||||
@@ -99,13 +161,82 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
res.end(JSON.stringify(req.zncAuth));
|
||||
});
|
||||
|
||||
app.get('/api/znc/token', authToken, (req, res) => {
|
||||
if (!req.zncAuthPolicyToken) {
|
||||
res.statusCode = 403;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'no_policy'}));
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(req.zncAuthPolicy));
|
||||
});
|
||||
app.delete('/api/znc/token', authToken, async (req, res) => {
|
||||
if (!req.zncAuthPolicyToken) {
|
||||
res.statusCode = 403;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'no_policy'}));
|
||||
return;
|
||||
}
|
||||
|
||||
await storage.removeItem('ZncProxyAuthPolicy.' + req.zncAuthPolicyToken!);
|
||||
|
||||
const tokens = new Set(await storage.getItem('ZncProxyAuthPolicies.' + req.zncAuthPolicyUser) ?? []);
|
||||
tokens.delete(req.zncAuthPolicyToken);
|
||||
await storage.setItem('ZncProxyAuthPolicies.' + req.zncAuthPolicyUser, [...tokens]);
|
||||
|
||||
res.statusCode = 204;
|
||||
res.end();
|
||||
});
|
||||
app.get('/api/znc/tokens', nsoAuth, async (req, res) => {
|
||||
const token_ids: string[] | undefined = await storage.getItem('ZncProxyAuthPolicies.' + req.zncAuth!.user.id);
|
||||
const tokens = (await Promise.all(token_ids?.map(async id => {
|
||||
const auth: AuthToken | undefined = await storage.getItem('ZncProxyAuthPolicy.' + id);
|
||||
if (!auth) return;
|
||||
return {
|
||||
token: id,
|
||||
user: auth.user,
|
||||
policy: auth.policy,
|
||||
created_at: auth.created_at,
|
||||
};
|
||||
}) ?? [])).filter(p => p);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({tokens}));
|
||||
});
|
||||
app.post('/api/znc/tokens', nsoAuth, bodyParser.json(), async (req, res) => {
|
||||
const token = uuidgen();
|
||||
const auth: AuthToken = {
|
||||
user: req.zncAuth!.user.id,
|
||||
policy: req.body.policy,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
await storage.setItem('ZncProxyAuthPolicy.' + token, auth);
|
||||
|
||||
const tokens = new Set(await storage.getItem('ZncProxyAuthPolicies.' + req.zncAuth!.user.id) ?? []);
|
||||
tokens.add(token);
|
||||
await storage.setItem('ZncProxyAuthPolicies.' + req.zncAuth!.user.id, [...tokens]);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({
|
||||
token,
|
||||
...auth,
|
||||
}));
|
||||
});
|
||||
|
||||
//
|
||||
// Announcements
|
||||
// This is cached for all users.
|
||||
//
|
||||
|
||||
let cached_announcements: Announcement[] | null = null;
|
||||
app.get('/api/znc/announcements', localAuth, nsoAuth, async (req, res) => {
|
||||
app.get('/api/znc/announcements', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.announcements) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, async (req, res) => {
|
||||
if (cached_announcements) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({
|
||||
@@ -167,18 +298,36 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/api/znc/friends', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/friends', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.list_friends) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({friends, updated}));
|
||||
res.end(JSON.stringify({
|
||||
friends: req.zncAuthPolicy?.friends ?
|
||||
friends.filter(f => req.zncAuthPolicy!.friends!.includes(f.nsaId)) : friends,
|
||||
updated,
|
||||
}));
|
||||
});
|
||||
|
||||
app.get('/api/znc/friends/presence', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/friends/presence', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.list_friends_presence) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
const presence: Record<string, Presence> = {};
|
||||
|
||||
for (const friend of friends) {
|
||||
if (req.zncAuthPolicy) {
|
||||
const p = req.zncAuthPolicy;
|
||||
if (p.friends_presence && !p.friends_presence.includes(friend.nsaId)) continue;
|
||||
if (p.friends && !p.friends_presence && !p.friends.includes(friend.nsaId)) continue;
|
||||
}
|
||||
|
||||
presence[friend.nsaId] = friend.presence;
|
||||
}
|
||||
|
||||
@@ -186,9 +335,14 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
res.end(JSON.stringify(presence));
|
||||
});
|
||||
|
||||
app.get('/api/znc/friend/:nsid', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/friend/:nsaid', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.friend) return tokenUnauthorised(req, res);
|
||||
if (req.zncAuthPolicy.friends && !req.zncAuthPolicy.friends.includes(req.params.nsaid)) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
const friend = friends.find(f => f.nsaId === req.params.nsid);
|
||||
const friend = friends.find(f => f.nsaId === req.params.nsaid);
|
||||
|
||||
if (!friend) {
|
||||
res.statusCode = 404;
|
||||
@@ -204,9 +358,15 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
res.end(JSON.stringify({friend, updated}));
|
||||
});
|
||||
|
||||
app.get('/api/znc/friend/:nsid/presence', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/friend/:nsaid/presence', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.friend_presence) return tokenUnauthorised(req, res);
|
||||
if (req.zncAuthPolicy.friends_presence && !req.zncAuthPolicy.friends_presence.includes(req.params.nsaid)) return tokenUnauthorised(req, res);
|
||||
if (req.zncAuthPolicy.friends && !req.zncAuthPolicy.friends_presence && !req.zncAuthPolicy.friends.includes(req.params.nsaid)) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
const friend = friends.find(f => f.nsaId === req.params.nsid);
|
||||
const friend = friends.find(f => f.nsaId === req.params.nsaid);
|
||||
|
||||
if (!friend) {
|
||||
res.statusCode = 404;
|
||||
@@ -222,7 +382,11 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
res.end(JSON.stringify(friend.presence));
|
||||
});
|
||||
|
||||
app.get('/api/znc/webservices', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/webservices', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.webservices) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
@@ -247,7 +411,11 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/znc/activeevent', localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
app.get('/api/znc/activeevent', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.activeevent) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getAppData, async (req, res) => {
|
||||
const [friends, webservices, activeevent, updated] = cached_appdata.get(req.zncAuth!.user.id)!;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
@@ -284,14 +452,23 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/api/znc/user', localAuth, nsoAuth, getUserData, async (req, res) => {
|
||||
app.get('/api/znc/user', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.current_user) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getUserData, async (req, res) => {
|
||||
const [user, updated] = cached_userdata.get(req.zncAuth!.user.id)!;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({user, updated}));
|
||||
});
|
||||
|
||||
app.get('/api/znc/user/presence', localAuth, nsoAuth, getUserData, async (req, res) => {
|
||||
app.get('/api/znc/user/presence', authToken, (req, res, next) => {
|
||||
if (!req.zncAuthPolicy) return next();
|
||||
if (!req.zncAuthPolicy.current_user_presence) return tokenUnauthorised(req, res);
|
||||
if (!('current_user_presence' in req.zncAuthPolicy) && !req.zncAuthPolicy.current_user) return tokenUnauthorised(req, res);
|
||||
next();
|
||||
}, localAuth, nsoAuth, getUserData, async (req, res) => {
|
||||
const [user, updated] = cached_userdata.get(req.zncAuth!.user.id)!;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
@@ -359,6 +536,24 @@ class ZncPresenceEventStream extends ZncNotifications {
|
||||
userNotifications: user,
|
||||
'friend-notifications': friend,
|
||||
friendNotifications: friend,
|
||||
'splatnet2-monitor-directory': undefined,
|
||||
splatnet2MonitorDirectory: undefined,
|
||||
'splatnet2-monitor-profile-image': false,
|
||||
splatnet2MonitorProfileImage: false,
|
||||
'splatnet2-monitor-favourite-stage': undefined,
|
||||
splatnet2MonitorFavouriteStage: undefined,
|
||||
'splatnet2-monitor-favourite-colour': undefined,
|
||||
splatnet2MonitorFavouriteColour: undefined,
|
||||
'splatnet2-monitor-battles': false,
|
||||
splatnet2MonitorBattles: false,
|
||||
'splatnet2-monitor-battle-summary-image': false,
|
||||
splatnet2MonitorBattleSummaryImage: false,
|
||||
'splatnet2-monitor-battle-images': false,
|
||||
splatnet2MonitorBattleImages: false,
|
||||
'splatnet2-monitor-coop': false,
|
||||
splatnet2MonitorCoop: false,
|
||||
'splatnet2-monitor-update-interval': 180,
|
||||
splatnet2MonitorUpdateInterval: 180,
|
||||
}, storage, token, nso, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ export * as friends from './friends.js';
|
||||
export * as presence from './presence.js';
|
||||
export * as notify from './notify.js';
|
||||
export * as httpServer from './http-server.js';
|
||||
export * as zncProxyTokens from './znc-proxy-tokens.js';
|
||||
|
||||
169
src/cli/nso/znc-proxy-tokens.ts
Normal file
169
src/cli/nso/znc-proxy-tokens.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import createDebug from 'debug';
|
||||
import fetch from 'node-fetch';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../nso.js';
|
||||
import { Argv, getToken, initStorage } from '../../util.js';
|
||||
import { AuthPolicy, AuthToken } from './http-server.js';
|
||||
|
||||
const debug = createDebug('cli:nso:znc-proxy-tokens');
|
||||
|
||||
export const command = 'znc-proxy-tokens <command>';
|
||||
export const desc = 'Manage access tokens for `nxapi nso http-server`';
|
||||
|
||||
interface AuthTokens {
|
||||
tokens: ({
|
||||
token: string;
|
||||
} & AuthToken)[];
|
||||
}
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.option('user', {
|
||||
describe: 'Nintendo Account ID',
|
||||
type: 'string',
|
||||
}).option('token', {
|
||||
describe: 'Nintendo Account session token',
|
||||
type: 'string',
|
||||
}).command('list', 'Lists access tokens', () => {}, async argv => {
|
||||
if (!argv.zncProxyUrl) {
|
||||
throw new Error('Requires --znc-proxy-url');
|
||||
}
|
||||
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken.' + usernsid);
|
||||
const {nso, data} = await getToken(storage, token, argv.zncProxyUrl);
|
||||
|
||||
const tokens = await nso.fetch<AuthTokens>('/tokens');
|
||||
|
||||
const table = new Table({
|
||||
head: [
|
||||
'Token',
|
||||
'Created at',
|
||||
'Access policy',
|
||||
],
|
||||
});
|
||||
|
||||
for (const auth of tokens.tokens) {
|
||||
table.push([
|
||||
auth.token,
|
||||
new Date(auth.created_at * 1000).toISOString(),
|
||||
JSON.stringify(auth.policy, null, 2) ?? 'No policy',
|
||||
]);
|
||||
}
|
||||
|
||||
if (!table.length) {
|
||||
console.warn('No access tokens');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(table.toString());
|
||||
}).command('create', 'Creates an access token', yargs => {
|
||||
return yargs.option('policy', {
|
||||
describe: 'Restrict allowed actions',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
}).option('policy-announcements', {
|
||||
describe: 'Allow access to /announcements',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-list-friends', {
|
||||
describe: 'Allow access to /friends',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-list-friends-presence', {
|
||||
describe: 'Allow access to /friends/presence',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-friend', {
|
||||
describe: 'Allow access to /friend/:id',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-friend-presence', {
|
||||
describe: 'Allow access to /friend/:id/presence',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-webservices', {
|
||||
describe: 'Allow access to /webservices',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-activeevent', {
|
||||
describe: 'Allow access to /activeevent',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-user', {
|
||||
describe: 'Allow access to /user',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-user-presence', {
|
||||
describe: 'Allow access to /user/presence',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('policy-friends', {
|
||||
describe: 'Restrict friends to listed Nintendo Switch account IDs',
|
||||
type: 'array',
|
||||
}).option('policy-friends-presence', {
|
||||
describe: 'Restrict friends to listed Nintendo Switch account IDs for presence only',
|
||||
type: 'array',
|
||||
});
|
||||
}, async argv => {
|
||||
if (!argv.zncProxyUrl) {
|
||||
throw new Error('Requires --znc-proxy-url');
|
||||
}
|
||||
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken.' + usernsid);
|
||||
const {nso, data} = await getToken(storage, token, argv.zncProxyUrl);
|
||||
|
||||
const policy: AuthPolicy | null = argv.policy ? {
|
||||
announcements: argv.policyAnnouncements,
|
||||
list_friends: argv.policyListFriends,
|
||||
list_friends_presence: argv.policyListFriendsPresence,
|
||||
friend: argv.policyFriend,
|
||||
friend_presence: argv.policyFriendPresence,
|
||||
webservices: argv.policyWebservices,
|
||||
activeevent: argv.policyActiveevent,
|
||||
current_user: argv.policyUser,
|
||||
current_user_presence: argv.policyUserPresence,
|
||||
|
||||
friends: argv.policyFriends as string[] | undefined,
|
||||
friends_presence: argv.policyFriendsPresence as string[] | undefined,
|
||||
} : null;
|
||||
|
||||
const auth = await nso.fetch<{token: string;} & AuthToken>('/tokens', 'POST', JSON.stringify({policy}), {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
console.warn('Created access token', auth);
|
||||
console.log(auth.token);
|
||||
}).command('revoke <token>', 'Deletes an access tokens', yargs => {
|
||||
return yargs.positional('token', {
|
||||
describe: 'Access token to delete',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
});
|
||||
}, async argv => {
|
||||
if (!argv.zncProxyUrl) {
|
||||
throw new Error('Requires --znc-proxy-url');
|
||||
}
|
||||
|
||||
const response = await fetch(argv.zncProxyUrl + '/token', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + argv.token,
|
||||
},
|
||||
});
|
||||
debug('fetch %s %s, response %d', 'DELETE', '/token', response.status);
|
||||
|
||||
if (response.status !== 204) {
|
||||
throw new Error('Unknown error ' + response.status);
|
||||
}
|
||||
|
||||
console.warn('Deleted access token');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user