Skip to content

Commit

Permalink
Implement OAuth 2.0 Authorization Server Metadata - closes #3143
Browse files Browse the repository at this point in the history
  • Loading branch information
benfrancis committed Aug 23, 2024
1 parent 3ef05a7 commit 9cab2bf
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const INTERNAL_LOGS_PATH = '/internal-logs';
export const LOGS_PATH = '/logs';
export const PUSH_PATH = '/push';
export const PING_PATH = '/ping';
export const WELL_KNOWN_PATH = '/.well-known';
export const PROXY_PATH = '/proxy';
export const EXTENSIONS_PATH = '/extensions';
// Remember we end up in the build/* directory so these paths looks slightly
Expand Down
31 changes: 31 additions & 0 deletions src/controllers/well-known_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Well-Known Controller
*
* Handles HTTP requests to /.well-known
*/

import express from 'express';
import * as Constants from '../constants';

function build(): express.Router {
const controller = express.Router();

/**
* OAuth 2.0 Authorization Server Metadata (RFC 8414)
*/
controller.get('/oauth-authorization-server', (request, response) => {
const origin = `${request.protocol}://${request.headers.host}`;
response.json({
issuer: origin,
authorization_endpoint: `${origin}${Constants.OAUTH_PATH}/authorize`,
token_endpoint: `${origin}${Constants.OAUTH_PATH}/token`,
response_types_supported: ['code'],
// Only expose top-level scopes to unauthenticated clients
scopes_supported: [Constants.THINGS_PATH, `${Constants.THINGS_PATH}:readwrite`],
});
});

return controller;
}

export default build;
2 changes: 2 additions & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import NotifiersController from './controllers/notifiers_controller';
import OAuthClientsController from './controllers/oauthclients_controller';
import OAuthController from './controllers/oauth_controller';
import PingController from './controllers/ping_controller';
import WellKnownController from './controllers/well-known_controller';
import ProxyController, { WithProxyMethods } from './controllers/proxy_controller';
import PushController from './controllers/push_controller';
import RootController from './controllers/root_controller';
Expand Down Expand Up @@ -155,6 +156,7 @@ class Router {
app.use(API_PREFIX + Constants.SETTINGS_PATH, nocache, SettingsController());
app.use(API_PREFIX + Constants.USERS_PATH, nocache, UsersController());
app.use(API_PREFIX + Constants.PING_PATH, nocache, PingController());
app.use(API_PREFIX + Constants.WELL_KNOWN_PATH, nocache, WellKnownController());

// Authenticated API routes
app.use(API_PREFIX + Constants.THINGS_PATH, nocache, auth, ThingsController());
Expand Down
21 changes: 21 additions & 0 deletions src/test/integration/oauth-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,27 @@ describe('oauth/', function () {
customCallbackHandler = customCallbackHandlerProvided || null;
}

it('serves OAuth metadata', async () => {
const res = await chai
.request(server)
.keepOpen()
.get('/.well-known/oauth-authorization-server')
.set('Accept', 'application/json');
expect(res.status).toEqual(200);
expect(res.body).toHaveProperty('issuer');
expect(res.body).toHaveProperty('authorization_endpoint');
expect(res.body.authorization_endpoint).toEqual(expect.stringContaining('authorize'));
expect(res.body).toHaveProperty('token_endpoint');
expect(res.body.token_endpoint).toEqual(expect.stringContaining('token'));
expect(res.body).toHaveProperty('response_types_supported');
expect(res.body.response_types_supported.length).toEqual(1);
expect(res.body.response_types_supported[0]).toEqual('code');
expect(res.body).toHaveProperty('scopes_supported');
expect(res.body.scopes_supported.length).toEqual(2);
expect(res.body.scopes_supported).toContain('/things');
expect(res.body.scopes_supported).toContain('/things:readwrite');
});

it('rejects request with no JWT', async () => {
setupOAuth();

Expand Down

0 comments on commit 9cab2bf

Please sign in to comment.