actually implement oauth2 pcke, drop image scope

This commit is contained in:
Evert Prants 2022-12-03 10:02:58 +02:00
parent 4d6267d40a
commit c3c297a9a5
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
11 changed files with 66 additions and 29 deletions

14
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.0.1",
"license": "UNLICENSED",
"dependencies": {
"@icynet/oauth2-provider": "^1.0.6",
"@icynet/oauth2-provider": "^1.0.7",
"@nestjs/common": "^9.0.11",
"@nestjs/core": "^9.0.11",
"@nestjs/platform-express": "^9.0.11",
@ -2142,9 +2142,9 @@
"dev": true
},
"node_modules/@icynet/oauth2-provider": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@icynet/oauth2-provider/-/oauth2-provider-1.0.6.tgz",
"integrity": "sha512-CsPQZB0Jbzxll4re34aPtZFVNkeeWtC4aW9UZCg8U57fPL8/Xe/2dfnigxdn4r9jVdd6d/qiFSh5x7wrFUmYrw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@icynet/oauth2-provider/-/oauth2-provider-1.0.7.tgz",
"integrity": "sha512-YdzkB8c/7BOUZaiKpeEFbLfttfH6kztDm+qUG3zqgZ6J+CXJMqtLnJtFu++bn8/okYikU1ErdZq2/4fetD1C+Q==",
"dependencies": {
"express": "^4.17.3",
"express-session": "^1.17.2"
@ -14155,9 +14155,9 @@
"dev": true
},
"@icynet/oauth2-provider": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@icynet/oauth2-provider/-/oauth2-provider-1.0.6.tgz",
"integrity": "sha512-CsPQZB0Jbzxll4re34aPtZFVNkeeWtC4aW9UZCg8U57fPL8/Xe/2dfnigxdn4r9jVdd6d/qiFSh5x7wrFUmYrw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@icynet/oauth2-provider/-/oauth2-provider-1.0.7.tgz",
"integrity": "sha512-YdzkB8c/7BOUZaiKpeEFbLfttfH6kztDm+qUG3zqgZ6J+CXJMqtLnJtFu++bn8/okYikU1ErdZq2/4fetD1C+Q==",
"requires": {
"express": "^4.17.3",
"express-session": "^1.17.2"

View File

@ -24,7 +24,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@icynet/oauth2-provider": "^1.0.6",
"@icynet/oauth2-provider": "^1.0.7",
"@nestjs/common": "^9.0.11",
"@nestjs/core": "^9.0.11",
"@nestjs/platform-express": "^9.0.11",

View File

@ -24,7 +24,7 @@ export class AppController {
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'],
scopes_supported: ['openid', 'profile', 'picture', 'email'],
claims_supported: [
'aud',
'exp',
@ -33,6 +33,7 @@ export class AppController {
'sub',
'name',
'preferred_username',
'nickname',
'profile',
'picture',
'updated_at',

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class pcke1670052416869 implements MigrationInterface {
name = 'pcke1670052416869';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`o_auth2_token\` ADD \`pcke\` text NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`o_auth2_token\` DROP COLUMN \`pcke\``,
);
}
}

View File

@ -73,16 +73,11 @@ export class ApiController {
userData.email_verified = true;
}
if (
(scope.includes('image') ||
scope.includes('picture') ||
scopelessAccess) &&
user.picture
) {
userData.image = `${this._config.get('app.base_url')}/uploads/${
if ((scope.includes('picture') || scopelessAccess) && user.picture) {
userData.picture = `${this._config.get('app.base_url')}/uploads/${
user.picture.file
}`;
userData.image_file = user.picture.file;
userData.picture_file = user.picture.file;
}
if (

View File

@ -6,6 +6,7 @@ export class CodeAdapter implements OAuth2CodeAdapter {
constructor(private _service: OAuth2Service) {}
ttl = 3600;
challengeMethods = ['plain', 'S256'];
async create(
userId: number,
@ -13,6 +14,8 @@ export class CodeAdapter implements OAuth2CodeAdapter {
scope: string | string[],
ttl: number,
nonce?: string,
codeChallenge?: string,
codeChallengeMethod?: 'plain' | 'S256',
): Promise<string> {
const client = await this._service.clientService.getById(clientId);
const user = await this._service.userService.getById(userId);
@ -24,6 +27,12 @@ export class CodeAdapter implements OAuth2CodeAdapter {
).join(' ');
const expiresAt = new Date(Date.now() + ttl * 1000);
const pcke =
codeChallenge && codeChallengeMethod
? `${this.challengeMethods.indexOf(
codeChallengeMethod,
)}:${codeChallenge}`
: null;
this._service.tokenService.insertToken(
accessToken,
@ -33,6 +42,7 @@ export class CodeAdapter implements OAuth2CodeAdapter {
expiresAt,
user,
nonce,
pcke,
);
return accessToken;
@ -49,11 +59,22 @@ export class CodeAdapter implements OAuth2CodeAdapter {
return null;
}
let codeChallenge: string;
let codeChallengeMethod: 'plain' | 'S256';
if (find.pcke) {
codeChallengeMethod = this.challengeMethods[
Number(find.pcke.substring(0, 1))
] as 'plain' | 'S256';
codeChallenge = find.pcke.substring(2);
}
return {
...find,
code: find.token,
client_id: find.client.client_id,
user_id: find.user.id,
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
};
}
@ -82,4 +103,11 @@ export class CodeAdapter implements OAuth2CodeAdapter {
checkTTL(code: OAuth2Code): boolean {
return code.expires_at.getTime() > Date.now();
}
getCodeChallenge(code: OAuth2Code) {
return {
method: code.code_challenge_method,
challenge: code.code_challenge,
};
}
}

View File

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

View File

@ -15,7 +15,7 @@ import { UserAdapter } from './adapter/user.adapter';
const SCOPE_DESCRIPTION: Record<string, string> = {
email: 'Email address',
image: 'Profile picture',
picture: 'Profile picture',
};
const ALWAYS_AVAILABLE = ['Username and display name'];
@ -40,10 +40,7 @@ export class OAuth2Service {
let disallowedScopes = [...ALWAYS_UNAVAILABLE];
Object.keys(SCOPE_DESCRIPTION).forEach((item) => {
if (
scope.includes(item) ||
(item === 'image' && scope.includes('picture'))
) {
if (scope.includes(item)) {
allowedScopes.push(SCOPE_DESCRIPTION[item]);
} else {
disallowedScopes.push(SCOPE_DESCRIPTION[item]);

View File

@ -21,7 +21,6 @@ export class OAuth2ClientService {
];
public availableScopes = [
'image',
'picture',
'profile',
'email',

View File

@ -29,6 +29,9 @@ export class OAuth2Token {
@Column({ nullable: true, type: 'text' })
nonce: string;
@Column({ nullable: true, type: 'text' })
pcke: string;
@Column({ type: 'text', nullable: true })
scope: string;

View File

@ -19,6 +19,7 @@ export class OAuth2TokenService {
expiry: Date,
user?: User,
nonce?: string,
pcke?: string,
): Promise<OAuth2Token> {
const newToken = new OAuth2Token();
newToken.client = client;
@ -28,6 +29,7 @@ export class OAuth2TokenService {
newToken.user = user;
newToken.expires_at = expiry;
newToken.nonce = nonce;
newToken.pcke = pcke;
await this.tokenRepository.save(newToken);