enable more route path name configurability
authorJustin Wind <justin.wind+git@gmail.com>
Sat, 12 Oct 2024 19:53:57 +0000 (12:53 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Sat, 12 Oct 2024 19:53:57 +0000 (12:53 -0700)
config/default.js
src/manager.js
src/service.js
test/src/service.js

index 3b3fdcb9d864fac4faf3027b3eca98980c0e51da..818bfe4d6301be9d338603a648a620036764d57c 100644 (file)
@@ -28,6 +28,7 @@ const defaultOptions = {
 
   // The terminal portions of API route path endpoints.
   route: {
+    static: 'static',
     authorization: 'auth',
     consent: 'consent',
     healthcheck: 'healthcheck',
@@ -37,6 +38,12 @@ const defaultOptions = {
     ticket: 'ticket',
     token: 'token',
     userinfo: 'userinfo',
+    admin: 'admin',
+    'admin-ticket': 'admin/ticket',
+    'admin-maintenance': 'admin/maintenance',
+    'auth-login': 'admin/login',
+    'auth-logout': 'admin/logout',
+    'auth-settings': 'admin/settings',
   },
 
   // Database options
index ce544b949a90603ef09b8d7448410aa264fcb9e0..f1ef18083413b75f5cde9daff2c2d2f666fc3dd4 100644 (file)
@@ -54,6 +54,9 @@ class Manager {
   }
 
 
+  /**
+   * Establish AMQP connection and plumbing.
+   */
   async _connectQueues() {
     await this.queuePublisher.connect();
     await this.queuePublisher.establishAMQPPlumbing(this.options.queues.ticketPublishName);
@@ -1766,7 +1769,7 @@ class Manager {
     }
 
     try {
-      ctx.token = await this.mysteryBox.unpack(ctx.parsedBody['token']);
+      ctx.token = await this.mysteryBox.unpack(token);
     } catch (e) {
       this.logger.debug(_scope, 'failed to unpack token', { error: e, ctx });
     }
index f3b7be0d4ef8770eca761dd05e98c6c286b070bd..e1f2a5a7a020392547dc7e348a7dffb04bf5e91f 100644 (file)
@@ -33,29 +33,26 @@ class Service extends Dingus {
     this.authenticator = new Authenticator(logger, db, options);
     this.sessionManager = new SessionManager(logger, this.authenticator, options);
     this.resourceAuthenticator = new ResourceAuthenticator(logger, db, options);
-    this.loginPath = `${options.dingus.proxyPrefix}/admin/login`;
-
-    // N.B. /admin routes not currently configurable
-    const route = (r) => `/${options.route[r]}`; // eslint-disable-line security/detect-object-injection
+    this.loginPath = this._routeExternal('auth-login'); // eslint-disable-line sonarjs/no-duplicate-string
 
     // Service discovery
-    this.on(['GET'], route('metadata'), this.handlerGetMeta.bind(this));
+    this.on(['GET'], this._route('metadata'), this.handlerGetMeta.bind(this));
     // Also respond with metadata on well-known oauth2 endpoint if base has no prefix
     if ((options?.dingus?.selfBaseUrl?.match(/\//g) || []).length === 3) {
       this.on(['GET'], '/.well-known/oauth-authorization-server', this.handlerGetMeta.bind(this));
     }
 
     // Primary endpoints
-    this.on(['GET'], route('authorization'), this.handlerGetAuthorization.bind(this));
-    this.on(['POST'], route('authorization'), this.handlerPostAuthorization.bind(this));
-    this.on(['POST'], route('consent'), this.handlerPostConsent.bind(this));
-    this.on(['POST'], route('revocation'), this.handlerPostRevocation.bind(this));
-    this.on(['POST'], route('ticket'), this.handlerPostTicket.bind(this));
-    this.on(['POST'], route('token'), this.handlerPostToken.bind(this));
+    this.on(['GET'], this._route('authorization'), this.handlerGetAuthorization.bind(this));
+    this.on(['POST'], this._route('authorization'), this.handlerPostAuthorization.bind(this));
+    this.on(['POST'], this._route('consent'), this.handlerPostConsent.bind(this));
+    this.on(['POST'], this._route('revocation'), this.handlerPostRevocation.bind(this));
+    this.on(['POST'], this._route('ticket'), this.handlerPostTicket.bind(this));
+    this.on(['POST'], this._route('token'), this.handlerPostToken.bind(this));
 
     // Resource endpoints
-    this.on('POST', route('introspection'), this.handlerPostIntrospection.bind(this));
-    this.on('POST', route('userinfo'), this.handlerPostUserInfo.bind(this));
+    this.on('POST', this._route('introspection'), this.handlerPostIntrospection.bind(this));
+    this.on('POST', this._route('userinfo'), this.handlerPostUserInfo.bind(this));
 
     // Information page about service
     this.on(['GET'], '/', this.handlerGetRoot.bind(this));
@@ -64,35 +61,58 @@ class Service extends Dingus {
     this.on(['POST'], '/', this.handlerWhaGwan.bind(this));
 
     // Give load-balancers something to check
-    this.on(['GET'], route('healthcheck'), this.handlerGetHealthcheck.bind(this));
+    this.on(['GET'], this._route('healthcheck'), this.handlerGetHealthcheck.bind(this));
 
     // These routes are intended for accessing static content during development.
     // In production, a proxy server would likely handle these first.
-    this.on(['GET'], '/static', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/static/`);
-    this.on(['GET'], '/static/', this.handlerGetStaticFile.bind(this), 'index.html');
-    this.on(['GET'], '/static/:file', this.handlerGetStaticFile.bind(this));
+    this.on(['GET'], this._route('static'), this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/static/`);
+    this.on(['GET'], this._route('static', ''), this.handlerGetStaticFile.bind(this), 'index.html');
+    this.on(['GET'], this._route('static', ':file'), this.handlerGetStaticFile.bind(this));
     this.on(['GET'], '/favicon.ico', this.handlerGetStaticFile.bind(this), 'favicon.ico');
     this.on(['GET'], '/robots.txt', this.handlerGetStaticFile.bind(this), 'robots.txt');
 
     // Profile and token management for authenticated sessions
-    this.on(['GET'], '/admin', this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/admin/`);
-    this.on(['GET'], '/admin/', this.handlerGetAdmin.bind(this));
-    this.on(['POST'], '/admin/', this.handlerPostAdmin.bind(this));
+    this.on(['GET'], this._route('admin'), this.handlerRedirect.bind(this), `${options.dingus.proxyPrefix}/admin/`);
+    this.on(['GET'], this._route('admin', ''), this.handlerGetAdmin.bind(this));
+    this.on(['POST'], this._route('admin', ''), this.handlerPostAdmin.bind(this));
 
     // Ticket-proffering interface for authenticated sessions
-    this.on(['GET'], '/admin/ticket', this.handlerGetAdminTicket.bind(this));
-    this.on(['POST'], '/admin/ticket', this.handlerPostAdminTicket.bind(this));
+    this.on(['GET'], this._route('admin-ticket'), this.handlerGetAdminTicket.bind(this));
+    this.on(['POST'], this._route('admin-ticket'), this.handlerPostAdminTicket.bind(this));
 
     // User authentication and session establishment
-    this.on(['GET'], '/admin/login', this.handlerGetAdminLogin.bind(this));
-    this.on(['POST'], '/admin/login', this.handlerPostAdminLogin.bind(this));
-    this.on(['GET'], '/admin/logout', this.handlerGetAdminLogout.bind(this));
-    this.on(['GET'], '/admin/settings', this.handlerGetAdminSettings.bind(this));
-    this.on(['POST'], '/admin/settings', this.handlerPostAdminSettings.bind(this));
+    this.on(['GET'], this._route('auth-login'), this.handlerGetAdminLogin.bind(this));
+    this.on(['POST'], this._route('auth-login'), this.handlerPostAdminLogin.bind(this));
+    this.on(['GET'], this._route('auth-logout'), this.handlerGetAdminLogout.bind(this));
+    this.on(['GET'], this._route('auth-settings'), this.handlerGetAdminSettings.bind(this));
+    this.on(['POST'], this._route('auth-settings'), this.handlerPostAdminSettings.bind(this));
 
     // Page for upkeep info et cetera
-    this.on(['GET'], '/admin/maintenance', this.handlerGetAdminMaintenance.bind(this));
+    this.on(['GET'], this._route('admin-maintenance'), this.handlerGetAdminMaintenance.bind(this));
+
+  }
+
+
+  /**
+   * Returns the configured route path for the given route name.
+   * @param {string} r route name
+   * @param {string=} t trailer to append to route
+   * @returns {string} route path
+   */
+  _route(r, t) {
+    return `/${this.options.route[r]}${t !== undefined ? '/' + t : ''}`; // eslint-disable-line security/detect-object-injection
+
+  }
 
+
+  /**
+   * Returns the external route path for the given route name.
+   * @param {string} r route name
+   * @param {string=} t trailer to append to route
+   * @returns {string} route path
+   */
+  _routeExternal(r, t) {
+    return this.options.dingus.proxyPrefix + this._route(r, t);
   }
 
 
index 55cfec2111621915d5eb5ce01422bc21ea8b6850..777552a1f982e920d617c31f56cac58b83715833 100644 (file)
@@ -62,6 +62,39 @@ describe('Service', function () {
     assert(service);
   });
 
+  describe('_route', function () {
+    it('returns route', function () {
+      const r = service._route('admin');
+      assert.strictEqual(r, '/admin');
+    });
+    it('returns trailing-slash route', function () {
+      const r = service._route('admin', '');
+      assert.strictEqual(r, '/admin/');
+    });
+    it('returns route with trailer', function () {
+      const r = service._route('admin', ':id');
+      assert.strictEqual(r, '/admin/:id');
+    });
+  }); // _route
+
+  describe('_routeExternal', function () {
+    beforeEach(function () {
+      service.options.dingus.proxyPrefix = '/prefix';
+    });
+    it('returns route', function () {
+      const r = service._routeExternal('admin');
+      assert.strictEqual(r, '/prefix/admin');
+    });
+    it('returns trailing-slash route', function () {
+      const r = service._routeExternal('admin', '');
+      assert.strictEqual(r, '/prefix/admin/');
+    });
+    it('returns route with trailer', function () {
+      const r = service._routeExternal('admin', ':id');
+      assert.strictEqual(r, '/prefix/admin/:id');
+    });
+  }); // _routeExternal
+
   describe('initialize', function () {
     it('covers', async function () {
       await service.initialize();