change order of endpoint & method for consistency
This commit is contained in:
parent
cc7b07dcec
commit
f5604e4a46
@ -21,7 +21,7 @@ class Registry {
|
||||
this.endpoints.forEach(ep => {
|
||||
let str = `${spacer}[${ep.resolveable}]\n${spacer}[${ep.methods.map(([ method ]) => method.toUpperCase()).join(', ') || 'NONE'}] ${ep.path}`;
|
||||
|
||||
for (const [ sub, method ] of ep.subpaths) str += `\n${spacer.repeat(2)} - ${method.toUpperCase()}: ${sub}`;
|
||||
for (const [ method, sub ] of ep.subpaths) str += `\n${spacer.repeat(2)} - ${method.toUpperCase()}: ${sub}`;
|
||||
out.push(str);
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,7 @@ class Api404 extends ApiEndpoint {
|
||||
super(server, {
|
||||
name: 'api404',
|
||||
path: '/*',
|
||||
loadOrder: 10
|
||||
loadOrder: 9
|
||||
});
|
||||
|
||||
this.methods = [
|
||||
|
@ -14,9 +14,9 @@ class LoginEndpoint extends ApiEndpoint {
|
||||
[ 'post', this.twoFactorRedirect.bind(this), [ server.authenticator.local ]]
|
||||
];
|
||||
this.subpaths = [
|
||||
[ '/fail', 'get', this.fail.bind(this) ],
|
||||
[ '/discord', 'get', server.authenticator.discord ],
|
||||
[ '/verify', 'post', this.twoFactor.bind(this), server.authenticator.authenticate ], // 2FA verification
|
||||
[ 'get', '/fail', this.fail.bind(this) ],
|
||||
[ 'get', '/discord', server.authenticator.discord ],
|
||||
[ 'post', '/verify', this.twoFactor.bind(this), server.authenticator.authenticate ], // 2FA verification
|
||||
];
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ class RegisterEndpoint extends ApiEndpoint {
|
||||
[ 'post', this.register.bind(this), [ this.notLoggedIn.bind(this) ]]
|
||||
];
|
||||
this.subpaths = [
|
||||
[ '/finalise', 'post', this.finaliseRegistration.bind(this), [ server.auth.authenticate ]],
|
||||
[ '/toggle', 'post', this.toggleRegistration.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]],
|
||||
[ '/code', 'get', this.registrationCode.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]]
|
||||
[ 'post', '/finalise', this.finaliseRegistration.bind(this), [ server.auth.authenticate ]],
|
||||
[ 'post', '/toggle', this.toggleRegistration.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]],
|
||||
[ 'get', '/code', this.registrationCode.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]]
|
||||
];
|
||||
this.middleware = [ ];
|
||||
|
||||
|
25
src/server/endpoints/api/Settings.js
Normal file
25
src/server/endpoints/api/Settings.js
Normal file
@ -0,0 +1,25 @@
|
||||
const { ApiEndpoint } = require("../../interfaces");
|
||||
|
||||
class SettingsEndpoint extends ApiEndpoint {
|
||||
|
||||
constructor (server) {
|
||||
super(server, {
|
||||
name: 'settings',
|
||||
path: '/settings'
|
||||
});
|
||||
|
||||
this.methods = [
|
||||
[ 'get', this.getSettings.bind(this) ]
|
||||
];
|
||||
|
||||
// this.middleware = [ server.auth.authenticate ];
|
||||
|
||||
}
|
||||
|
||||
getSettings (req, res) {
|
||||
res.json(this.server.clientSettings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = SettingsEndpoint;
|
@ -1,10 +1,17 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { ApiEndpoint } = require("../../interfaces");
|
||||
const { UtilMiddleware } = require('../../middleware');
|
||||
const pkg = require('../../../../package.json');
|
||||
const { Util } = require("../../../util");
|
||||
|
||||
const qrcode = require('qrcode');
|
||||
const { authenticator } = require('otplib');
|
||||
|
||||
const { inspect } = require('node:util');
|
||||
|
||||
// Populated by the constructor
|
||||
const ServiceProfileLinks = {};
|
||||
|
||||
class UserEndpoint extends ApiEndpoint {
|
||||
|
||||
constructor (server) {
|
||||
@ -19,11 +26,18 @@ class UserEndpoint extends ApiEndpoint {
|
||||
];
|
||||
|
||||
this.subpaths = [
|
||||
[ '/2fa', 'get', this.twoFactor.bind(this) ],
|
||||
[ '/2fa/disable', 'get', this.disable2fa.bind(this) ],
|
||||
[ '/2fa/verify', 'post', this.verify2fa.bind(this) ],
|
||||
[ '/applications', 'get', this.applications.bind(this) ],
|
||||
[ '/applications', 'post', this.createApplication.bind(this) ],
|
||||
// 2 Factor Authentication
|
||||
[ 'get', '/2fa', this.twoFactor.bind(this) ],
|
||||
[ 'post', '/2fa/disable', this.disable2fa.bind(this) ],
|
||||
[ 'post', '/2fa/verify', this.verify2fa.bind(this) ],
|
||||
|
||||
// 3rd Party Connections
|
||||
[ 'get', '/connect/:service', this.connectOAuth.bind(this) ],
|
||||
[ 'get', '/connect/:service/finalise', this.connectOAuthFinalise.bind(this) ],
|
||||
|
||||
// Applications
|
||||
[ 'get', '/applications', this.applications.bind(this) ],
|
||||
[ 'post', '/applications', this.createApplication.bind(this) ],
|
||||
];
|
||||
|
||||
this.middleware = [
|
||||
@ -31,29 +45,18 @@ class UserEndpoint extends ApiEndpoint {
|
||||
UtilMiddleware.requireBody
|
||||
];
|
||||
|
||||
server.OAuthProviders.forEach(provider => {
|
||||
ServiceProfileLinks[provider.name.toLowerCase()] = provider.profileURL;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
user (req, res) {
|
||||
return res.json(req.user.safeJson);
|
||||
}
|
||||
|
||||
async applications (req, res) {
|
||||
const { user } = req;
|
||||
const applications = await user.fetchApplications();
|
||||
res.json(Object.values(applications).map(app => app.safeJson));
|
||||
}
|
||||
|
||||
async createApplication (req, res) {
|
||||
const { body, user } = req;
|
||||
const { name } = body;
|
||||
if (!name) return res.status(400).send('Missing name');
|
||||
const application = await user.createApplication(name);
|
||||
res.json(application.safeJson);
|
||||
|
||||
}
|
||||
|
||||
async twoFactor (req, res) {
|
||||
|
||||
|
||||
const { user } = req;
|
||||
if (user._2fa) return res.status(400).send('2FA already enabled');
|
||||
|
||||
@ -89,7 +92,7 @@ class UserEndpoint extends ApiEndpoint {
|
||||
async disable2fa (req, res) {
|
||||
const { user, body } = req;
|
||||
if (!body) return res.status(400).send('Missing body');
|
||||
|
||||
|
||||
const { code } = body;
|
||||
if (!code) return res.status(400).send('Missing code');
|
||||
if (!user._otpSecret || !user._2fa) return res.status(400).send('2FA not enabled');
|
||||
@ -101,6 +104,84 @@ class UserEndpoint extends ApiEndpoint {
|
||||
res.send('Disabled 2FA');
|
||||
}
|
||||
|
||||
async connectOAuth (req, res) {
|
||||
const { params } = req;
|
||||
const service = params.service.toLowerCase();
|
||||
if (!this.server.OAuthProviders.some(provider => provider.name.toLowerCase() === service)) return res.status(404).send('Provider not found');
|
||||
|
||||
const authoriseLink = this.server.auth.getOAuthLink(service, { redirect_uri: this.redirectURI(service) });
|
||||
res.redirect(authoriseLink);
|
||||
}
|
||||
|
||||
async connectOAuthFinalise (req, res) {
|
||||
const { params, query, user } = req;
|
||||
const service = params.service.toLowerCase();
|
||||
if (!this.server.OAuthProviders.some(provider => provider.name.toLowerCase() === service)) return res.status(404).send('Provider not found');
|
||||
const { code } = query;
|
||||
|
||||
const OAuthParams = this.server.auth.getOAuthTokenParams(service);
|
||||
const tokenLink = OAuthParams.link;
|
||||
|
||||
const body = {
|
||||
client_id: OAuthParams.clientID,
|
||||
client_secret: OAuthParams.clientSecret,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: this.redirectURI(service),
|
||||
code
|
||||
};
|
||||
|
||||
const response = await Util.post(tokenLink, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body
|
||||
});
|
||||
|
||||
if (response.status === 400) return res.status(400).send('Invalid code');
|
||||
if (response.status === 401) return res.status(401).end();
|
||||
if (!response.success) {
|
||||
this.logger.error(`Error in OAuth: ${response.status} ${inspect(response.data)}`);
|
||||
return res.status(500).end();
|
||||
}
|
||||
|
||||
const serviceProfileLink = ServiceProfileLinks[service];
|
||||
const profile = await Util.get(serviceProfileLink, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${response.data.access_token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (profile.status !== 200) {
|
||||
this.logger.error(`Non 200 code while fetching profile: ${profile.status} ${inspect(profile.data)}`);
|
||||
return res.status(500).end();
|
||||
}
|
||||
|
||||
user.addExternalProfile(service, profile.data);
|
||||
await user.save();
|
||||
|
||||
res.send(`Successfully linked ${user.username} with ${profile.data.username} (${profile.data.id})`);
|
||||
|
||||
}
|
||||
|
||||
async applications (req, res) {
|
||||
const { user } = req;
|
||||
const applications = await user.fetchApplications();
|
||||
res.json(Object.values(applications).map(app => app.safeJson));
|
||||
}
|
||||
|
||||
async createApplication (req, res) {
|
||||
const { body, user } = req;
|
||||
const { name } = body;
|
||||
if (!name) return res.status(400).send('Missing name');
|
||||
const application = await user.createApplication(name);
|
||||
res.json(application.safeJson);
|
||||
|
||||
}
|
||||
|
||||
redirectURI (service) {
|
||||
return encodeURI(`${this.server.baseURL}/api/user/connect/${service}/finalise`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserEndpoint;
|
@ -11,9 +11,9 @@ class UsersEndpoint extends ApiEndpoint {
|
||||
|
||||
this.methods.push([ 'get', this.getUsers.bind(this), [ server.auth.createAuthoriser('administrator', 10) ]]);
|
||||
this.subpaths = [
|
||||
[ '/:userid', 'get', this.user.bind(this), [ server.auth.createAuthoriser('administrator', 10) ]],
|
||||
[ '/:userid/avatar', 'get', this.avatar.bind(this) ],
|
||||
[ '/:userid/applications', 'get', this.userApplications.bind(this), [ server.auth.createAuthoriser('administrator', 10) ]]
|
||||
[ 'get', '/:userid', this.user.bind(this), [ server.auth.createAuthoriser('administrator', 10) ]],
|
||||
[ 'get', '/:userid/avatar', this.avatar.bind(this) ],
|
||||
[ 'get', '/:userid/applications', this.userApplications.bind(this), [ server.auth.createAuthoriser('administrator', 10) ]]
|
||||
];
|
||||
// this.middleware = [ server.auth.createAuthoriser('administrator', 10) ];
|
||||
|
||||
|
@ -15,7 +15,7 @@ class Endpoint {
|
||||
|
||||
// Subpaths that should exist on *all* endpoints, the subpaths property can be overwritten, so storing these separately to ensure they exist
|
||||
this._subpaths = [
|
||||
[ '/debug', 'post', this.toggleDebug.bind(this), [ server.authenticator.createAuthoriser('developer') ]]
|
||||
[ 'post', '/debug', this.toggleDebug.bind(this), [ server.authenticator.createAuthoriser('developer') ]]
|
||||
];
|
||||
|
||||
this.middleware = [];
|
||||
@ -44,7 +44,7 @@ class Endpoint {
|
||||
|
||||
this.subpaths = [ ...this._subpaths, ...this.subpaths ];
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [ sub, method, cb, mw = [] ] of this.subpaths) {
|
||||
for (let [ method, sub, cb, mw = [] ] of this.subpaths) {
|
||||
if (typeof method !== 'string') throw new Error(`Invalid method parameter type in Endpoint ${this.name} subpath ${sub}`);
|
||||
if (!this.middleware.length && !mw.length && !cb) throw new Error(`Cannot have endpoint with no handler and no middleware, expecting at least one to be defined`);
|
||||
if (typeof mw === 'function') mw = [ mw ];
|
||||
|
Loading…
Reference in New Issue
Block a user