Skip to content

Commit

Permalink
Adding a bunch of properties in the introspect response.
Browse files Browse the repository at this point in the history
  • Loading branch information
evert committed Nov 21, 2023
1 parent 5cc6802 commit 07014d1
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 89 deletions.
3 changes: 2 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Changelog
* It wasn't possible to see a full principal even if a user had
`a12n:principal:list` privilege.
* Added new privilege for changing passwords: `a12n:user:change-password`.
* Introspection endpoint now returns the 'sub' property.
* Introspection endpoint now returns the `exp`, `sub`, `aud` and `iss`
properties.


0.24.0 (2023-11-09)
Expand Down
20 changes: 17 additions & 3 deletions src/introspect/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { NotFound } from '@curveball/http-errors';
import * as oauth2Service from '../oauth2/service';
import { OAuth2Token } from '../oauth2/types';
import * as privilegeService from '../privilege/service';
import { accessToken, inactive, refreshToken } from './formats/json';
import * as oauth2ClientService from '../oauth2-client/service';
import { introspectResponse, inactive } from './formats/json';

/**
* The /introspect endpoint allows a client to get more information
Expand Down Expand Up @@ -69,14 +70,27 @@ class IntrospectionController extends Controller {
}
if (foundToken) {
const privileges = (await privilegeService.get(foundToken.principal)).getAll();
const client = await oauth2ClientService.findById(foundToken.clientId);

Check failure on line 73 in src/introspect/controller.ts

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed

switch (foundTokenType!) {

case 'accessToken' :
ctx.response.body = accessToken(ctx.request.origin, foundToken, privileges);
ctx.response.body = introspectResponse({
tokenType: 'bearer',
origin: ctx.request.origin,
token: foundToken,
client,
privileges,
});
break;
case 'refreshToken' :
ctx.response.body = refreshToken(ctx.request.origin, foundToken, privileges);
ctx.response.body = introspectResponse({
tokenType: 'refresh_token',
origin: ctx.request.origin,
token: foundToken,
client,
privileges,
});
break;
}
return;
Expand Down
70 changes: 47 additions & 23 deletions src/introspect/formats/json.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,64 @@
import { OAuth2Token } from '../../oauth2/types';
import { PrivilegeMap } from '../../privilege/types';
import * as url from 'url';
import { OAuth2Client } from '../../types';

export function accessToken(origin: string, token: OAuth2Token, privileges: PrivilegeMap) {
type IntrospectInfo = {
privileges: PrivilegeMap,

Check failure on line 7 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
client: OAuth2Client,

Check failure on line 8 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
token: OAuth2Token,

Check failure on line 9 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
tokenType: 'bearer' | 'refresh_token',

Check failure on line 10 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
origin: string,

Check failure on line 11 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
}

return {
active: true,
scope: Object.values(privileges).join(' '),
privileges: privileges,
client_id: token.clientId,
username: token.principal.nickname,
token_type: 'bearer',
exp: token.accessTokenExpires,
sub: url.resolve(origin, token.principal.href),
_links: {
'authenticated-as': {
href: url.resolve(origin, token.principal.href),
}

/**
* Defined in:
*
* https://www.rfc-editor.org/rfc/rfc7662#section-2.2
*/
type IntrospectResponse = {
active: boolean;
scope?: string;
client_id?: string;
username?: string;
token_type?: 'bearer' | 'refresh_token',

Check failure on line 25 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
exp?: number;
iat?: number;
nbf?: number;
sub?: string;
aud?: string;
iss?: string;
jti?: string;

/**
* A12n additions.
*/
privileges: PrivilegeMap,

Check failure on line 37 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
_links: {
'authenticated-as': {
href: string,

Check failure on line 40 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
}

Check failure on line 41 in src/introspect/formats/json.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected a semicolon
};
}

}

export function refreshToken(origin: string, token: OAuth2Token, privileges: PrivilegeMap) {

export function introspectResponse(info: IntrospectInfo): IntrospectResponse {
return {
active: true,
scope: Object.values(privileges).join(' '),
privileges: privileges,
client_id: token.clientId,
username: token.principal.nickname,
token_type: 'refresh_token',
sub: url.resolve(origin, token.principal.href),
scope: Object.values(info.privileges).join(' '),
privileges: info.privileges,
client_id: info.client.clientId,
username: info.token.principal.nickname,
token_type: info.tokenType,
exp: info.tokenType === 'refresh_token' ? info.token.refreshTokenExpires : info.token.accessTokenExpires,
sub: url.resolve(info.origin, info.token.principal.href),
aud: url.resolve(info.origin, info.client.app.href),
iss: info.origin,
_links: {
'authenticated-as': {
href: url.resolve(origin, token.principal.href),
href: url.resolve(info.origin, info.token.principal.href),
}
}
};
Expand Down
3 changes: 1 addition & 2 deletions src/oauth2-client/controller/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { Forbidden, UnprocessableEntity } from '@curveball/http-errors';

import * as hal from '../formats/hal';
import { PrincipalService } from '../../principal/service';
import { GrantType } from '../../types';
import { OAuth2Client } from '../types';
import { GrantType, OAuth2Client } from '../../types';
import { findByApp, create } from '../service';
import { generatePublicId, generateSecretToken } from '../../crypto';

Expand Down
3 changes: 1 addition & 2 deletions src/oauth2-client/formats/hal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { App } from '../../types';
import { OAuth2Client } from '../types';
import { App, OAuth2Client } from '../../types';
import { HalResource } from 'hal-types';

export function collection(app: App, clients: OAuth2Client[]): HalResource {
Expand Down
3 changes: 1 addition & 2 deletions src/oauth2-client/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { NotFound, Unauthorized, Conflict } from '@curveball/http-errors';
import { Oauth2ClientsRecord } from 'knex/types/tables';
import { wrapError, UniqueViolationError } from 'db-errors';

import { OAuth2Client } from './types';
import { PrincipalService } from '../principal/service';
import db, { insertAndGetId } from '../database';
import { InvalidRequest } from '../oauth2/errors';
import parseBasicAuth from './parse-basic-auth';
import { App, GrantType } from '../types';
import { App, GrantType, OAuth2Client } from '../types';

export async function findByClientId(clientId: string): Promise<OAuth2Client> {

Expand Down
44 changes: 0 additions & 44 deletions src/oauth2-client/types.ts

This file was deleted.

8 changes: 6 additions & 2 deletions src/oauth2/controller/active-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import { PrincipalService } from '../../principal/service';
import * as oauth2Service from '../service';
import * as oauth2ClientService from '../../oauth2-client/service';

import { Principal, User, App } from '../../types';
import { OAuth2Client } from '../../oauth2-client/types';
import {
App,
OAuth2Client,
Principal,
User,
} from '../../types';

import { OAuth2Token } from '../types';

Expand Down
5 changes: 3 additions & 2 deletions src/oauth2/controller/authorize.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as querystring from 'querystring';
import Controller from '@curveball/controller';
import { Context } from '@curveball/core';
import { NotFound, NotImplemented } from '@curveball/http-errors';
import * as querystring from 'querystring';

import { InvalidClient, InvalidRequest, UnsupportedGrantType } from '../errors';
import * as oauth2Service from '../service';
import { CodeChallengeMethod } from '../types';
import { OAuth2Client } from '../../oauth2-client/types';
import { OAuth2Client } from '../../types';
import log from '../../log/service';
import { EventType } from '../../log/types';
import { findByClientId } from '../../oauth2-client/service';
Expand Down
2 changes: 1 addition & 1 deletion src/oauth2/controller/revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Context } from '@curveball/core';
import log from '../../log/service';
import { EventType } from '../../log/types';
import { revokeByAccessRefreshToken } from '../service';
import { OAuth2Client } from '../../oauth2-client/types';
import { OAuth2Client } from '../../types';
import {
getOAuth2ClientFromBasicAuth,
getOAuth2ClientFromBody,
Expand Down
3 changes: 1 addition & 2 deletions src/oauth2/controller/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import log from '../../log/service';
import { EventType } from '../../log/types';
import { PrincipalService } from '../../principal/service';
import * as userService from '../../user/service';
import { User } from '../../types';
import { User, OAuth2Client } from '../../types';
import { InvalidGrant, InvalidRequest, UnsupportedGrantType } from '../errors';
import * as oauth2Service from '../service';
import { OAuth2Client } from '../../oauth2-client/types';
import {
getOAuth2ClientFromBasicAuth,
getOAuth2ClientFromBody,
Expand Down
2 changes: 1 addition & 1 deletion src/oauth2/formats/csv.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OAuth2Token } from '../types';
import { OAuth2Client } from '../../oauth2-client/types';
import { OAuth2Client } from '../../types';

import { stringify } from 'csv-stringify/sync';

Expand Down
3 changes: 1 addition & 2 deletions src/oauth2/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { User, App } from '../types';
import { OAuth2Client } from '../oauth2-client/types';
import { User, App, OAuth2Client } from '../types';
import { generateSecretToken } from '../crypto';
import { getSetting } from '../server-settings';
import { createPrivateKey, KeyObject, createPublicKey } from 'crypto';
Expand Down
3 changes: 1 addition & 2 deletions src/oauth2/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { getSetting } from '../server-settings';
import { PrincipalService } from '../principal/service';
import { InvalidGrant, InvalidRequest, UnauthorizedClient } from './errors';
import { CodeChallengeMethod, OAuth2Code, OAuth2Token } from './types';
import { OAuth2Client } from '../oauth2-client/types';
import { generateSecretToken } from '../crypto';
import { generateJWTAccessToken, generateJWTIDToken } from './jwt';
import { Oauth2TokensRecord, Oauth2CodesRecord } from 'knex/types/tables';
import { App, User, GrantType } from '../types';
import { App, User, GrantType, OAuth2Client } from '../types';
import * as userAppPermissionsService from '../user-app-permissions/service';

const oauth2TokenFields: (keyof Oauth2TokensRecord)[] = [
Expand Down
43 changes: 43 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,46 @@ export type PrincipalStats = {
app: number;
group: number;
};

/**
* The OAuth2 client refers to a single (programmatic) client, accessing
* an API.
*
* OAuth2 clients are associated to 'Apps'. Each 'app' may have multiple
* OAuth2 clients.
*/
export type OAuth2Client = {

/**
* Unique, internal id.
*/
id: number;

/**
* Route to this client
*/
href: string;

/**
* A string that's used to configure OAuth2 clients.
*/
clientId: string;


/**
* A secret string. This is hashed using bcrypt2
*/
clientSecret: string;

app: App;

/**
* List of allowed grantTypes this client may use.
*/
allowedGrantTypes: GrantType[];

/**
* Require PKCE for authorization_code flows.
*/
requirePkce: boolean;
};

0 comments on commit 07014d1

Please sign in to comment.