openid-configuration

This commit is contained in:
Evert Prants 2022-12-02 22:33:02 +02:00
parent 5a40b44dd1
commit e394880539
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
8 changed files with 76 additions and 4 deletions

View File

@ -1,10 +1,46 @@
import { Controller, Get, Redirect } from '@nestjs/common'; import { Controller, Get, Redirect } from '@nestjs/common';
import { ConfigurationService } from './modules/config/config.service';
@Controller() @Controller()
export class AppController { export class AppController {
constructor(private config: ConfigurationService) {}
@Get() @Get()
@Redirect('/account/general') @Redirect('/account/general')
getHello() { getHello() {
return; return;
} }
@Get('/.well-known/openid-configuration')
openidConfiguration() {
const base = this.config.get<string>('app.base_url');
return {
issuer: this.config.get('jwt.issuer'),
authorization_endpoint: `${base}/oauth2/authorize`,
token_endpoint: `${base}/oauth2/token`,
jwks_uri: `${base}/oauth2/jwks`,
userinfo_endpoint: `${base}/api/user`,
introspection_endpoint: `${base}/oauth2/introspect`,
response_types_supported: ['code', 'id_token'],
id_token_signing_alg_values_supported: [this.config.get('jwt.algorithm')],
subject_types_supported: ['public'],
scopes_supported: ['openid', 'profile', 'email'],
claims_supported: [
'aud',
'exp',
'iat',
'iss',
'sub',
'name',
'preferred_username',
'profile',
'picture',
'updated_at',
'email',
'email_verified',
],
code_challenge_methods_supported: ['plain', 'S256'],
grant_types_supported: ['authorization_code', 'refresh_token'],
};
}
} }

View File

@ -17,4 +17,12 @@ export const jwtProviders = [
useFactory: async (path: string) => readFile(join(path, 'jwt.public.pem')), useFactory: async (path: string) => readFile(join(path, 'jwt.public.pem')),
inject: ['PRIVATE_PATH'], inject: ['PRIVATE_PATH'],
} as FactoryProvider<Buffer>, } as FactoryProvider<Buffer>,
{
provide: 'JWT_JWKS',
useFactory: async (path: string) =>
JSON.parse(
await readFile(join(path, 'jwks.json'), { encoding: 'utf-8' }),
),
inject: ['PRIVATE_PATH'],
} as FactoryProvider<Record<string, unknown>>,
]; ];

View File

@ -12,6 +12,7 @@ export class JWTService {
constructor( constructor(
@Inject('JWT_PRIVATE_KEY') private _privateKey: Buffer, @Inject('JWT_PRIVATE_KEY') private _privateKey: Buffer,
@Inject('JWT_PUBLIC_KEY') private _publicKey: Buffer, @Inject('JWT_PUBLIC_KEY') private _publicKey: Buffer,
@Inject('JWT_JWKS') private _jwks: Record<string, unknown>,
private _config: ConfigurationService, private _config: ConfigurationService,
) {} ) {}
@ -41,4 +42,13 @@ export class JWTService {
audience, audience,
}) as jwt.JwtPayload; }) as jwt.JwtPayload;
} }
// TODO: generate on-the-go
public get jwks() {
return {
alg: this._config.get('jwt.algorithm'),
...this._jwks,
use: 'sig',
};
}
} }

View File

@ -26,7 +26,9 @@ export class IcyJWTAdapter implements JWTAdapter {
} }
if ( if (
(scope.includes('image') || scope.includes('profile')) && (scope.includes('image') ||
scope.includes('picture') ||
scope.includes('profile')) &&
user.picture user.picture
) { ) {
userData.picture = `${this._client.config.get('app.base_url')}/uploads/${ userData.picture = `${this._client.config.get('app.base_url')}/uploads/${

View File

@ -15,6 +15,6 @@ import { OAuth2Service } from './oauth2.service';
JWTModule, JWTModule,
], ],
providers: [OAuth2Service], providers: [OAuth2Service],
exports: [OAuth2Service], exports: [OAuth2Service, JWTModule],
}) })
export class OAuth2Module {} export class OAuth2Module {}

View File

@ -40,7 +40,10 @@ export class OAuth2Service {
let disallowedScopes = [...ALWAYS_UNAVAILABLE]; let disallowedScopes = [...ALWAYS_UNAVAILABLE];
Object.keys(SCOPE_DESCRIPTION).forEach((item) => { Object.keys(SCOPE_DESCRIPTION).forEach((item) => {
if (scope.includes(item)) { if (
scope.includes(item) ||
(item === 'image' && scope.includes('picture'))
) {
allowedScopes.push(SCOPE_DESCRIPTION[item]); allowedScopes.push(SCOPE_DESCRIPTION[item]);
} else { } else {
disallowedScopes.push(SCOPE_DESCRIPTION[item]); disallowedScopes.push(SCOPE_DESCRIPTION[item]);

View File

@ -22,6 +22,8 @@ export class OAuth2ClientService {
public availableScopes = [ public availableScopes = [
'image', 'image',
'picture',
'profile',
'email', 'email',
'privileges', 'privileges',
'management', 'management',

View File

@ -14,6 +14,7 @@ import { Scope } from 'src/decorators/scope.decorator';
import { CurrentUser } from 'src/decorators/user.decorator'; import { CurrentUser } from 'src/decorators/user.decorator';
import { OAuth2Guard } from 'src/guards/oauth2.guard'; import { OAuth2Guard } from 'src/guards/oauth2.guard';
import { ConfigurationService } from 'src/modules/config/config.service'; import { ConfigurationService } from 'src/modules/config/config.service';
import { JWTService } from 'src/modules/jwt/jwt.service';
import { User } from 'src/modules/objects/user/user.entity'; import { User } from 'src/modules/objects/user/user.entity';
import { OAuth2Service } from '../../oauth2/oauth2.service'; import { OAuth2Service } from '../../oauth2/oauth2.service';
@ -23,6 +24,7 @@ export class OAuth2Controller {
constructor( constructor(
private _service: OAuth2Service, private _service: OAuth2Service,
private _config: ConfigurationService, private _config: ConfigurationService,
private _jwt: JWTService,
) {} ) {}
// These requests are just passed straight on to the provider controller // These requests are just passed straight on to the provider controller
@ -96,7 +98,9 @@ export class OAuth2Controller {
} }
if ( if (
(scope.includes('image') || scope.includes('user:image')) && (scope.includes('image') ||
scope.includes('picture') ||
scope.includes('user:image')) &&
user.picture user.picture
) { ) {
userData.image = `${this._config.get('app.base_url')}/uploads/${ userData.image = `${this._config.get('app.base_url')}/uploads/${
@ -114,4 +118,11 @@ export class OAuth2Controller {
return userData; return userData;
} }
@Get('jwks')
getJWKS() {
return {
keys: [this._jwt.jwks],
};
}
} }