initial implementation of pcke
This commit is contained in:
parent
ef4a5abac9
commit
4babfa801a
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@icynet/oauth2-provider",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "OAuth2.0 Provider for Icy Network",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
InvalidScope,
|
||||
AccessDenied,
|
||||
InteractionRequired,
|
||||
InvalidGrant,
|
||||
} from '../model/error';
|
||||
import { OAuth2User } from '../model/model';
|
||||
import { data as dataResponse } from '../utils/response';
|
||||
@ -138,6 +139,13 @@ export const authorization = wrap(async (req, res) => {
|
||||
req.oauth2.logger.debug('User fetched from request');
|
||||
}
|
||||
|
||||
const codeChallenge = req.query.code_challenge as string;
|
||||
const codeChallengeMethod = (req.query.code_challenge_method as 'plain' | 'S256') || 'plain';
|
||||
|
||||
if (codeChallengeMethod && !['plain', 'S256'].includes(codeChallengeMethod)) {
|
||||
throw new InvalidGrant('Invalid code challenge method');
|
||||
}
|
||||
|
||||
const prompt = ((req.query.prompt || '') as string).split(' ');
|
||||
let resObj: Record<string, string | number> = {};
|
||||
let consented = false;
|
||||
@ -191,6 +199,8 @@ export const authorization = wrap(async (req, res) => {
|
||||
scope,
|
||||
oauth2.model.code.ttl,
|
||||
req.query.nonce as string,
|
||||
codeChallenge,
|
||||
codeChallengeMethod
|
||||
);
|
||||
|
||||
resObj = { code: data, ...resObj };
|
||||
|
@ -94,7 +94,8 @@ export const token = wrap(async (req, res) => {
|
||||
tokenResponse = await tokens.authorizationCode(
|
||||
oauth2,
|
||||
client,
|
||||
req.body.code
|
||||
req.body.code,
|
||||
req.body.code_verifier
|
||||
);
|
||||
break;
|
||||
case 'password':
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
OAuth2Code,
|
||||
OAuth2TokenResponse,
|
||||
} from '../../model/model';
|
||||
import { createS256, safeCompare } from '../../utils/crypto-tools';
|
||||
|
||||
/**
|
||||
* Issue an access token by authorization code
|
||||
@ -16,7 +17,8 @@ import {
|
||||
export async function authorizationCode(
|
||||
oauth2: OAuth2,
|
||||
client: OAuth2Client,
|
||||
providedCode: string
|
||||
providedCode: string,
|
||||
codeVerifier?: string,
|
||||
): Promise<OAuth2TokenResponse> {
|
||||
const respObj: OAuth2TokenResponse = {
|
||||
token_type: 'bearer',
|
||||
@ -58,6 +60,26 @@ export async function authorizationCode(
|
||||
const userId = oauth2.model.code.getUserId(code);
|
||||
const clientId = oauth2.model.code.getClientId(code);
|
||||
|
||||
if (oauth2.model.code.getCodeChallenge) {
|
||||
const { challenge, method } = oauth2.model.code.getCodeChallenge(code);
|
||||
|
||||
if (challenge && method) {
|
||||
if (!codeVerifier) {
|
||||
throw new InvalidGrant('Code verifier is required!');
|
||||
}
|
||||
|
||||
if (method === 'plain' && !safeCompare(challenge, codeVerifier)) {
|
||||
throw new InvalidGrant('Invalid code verifier!');
|
||||
}
|
||||
|
||||
if (method === 'S256' && !safeCompare(createS256(codeVerifier), challenge)) {
|
||||
throw new InvalidGrant('Invalid code verifier!');
|
||||
}
|
||||
}
|
||||
|
||||
oauth2.logger.debug('Code passed PCKE check');
|
||||
}
|
||||
|
||||
if (oauth2.model.refreshToken.invalidateOld) {
|
||||
try {
|
||||
await oauth2.model.refreshToken.removeByUserIdClientId(userId, clientId);
|
||||
|
@ -32,6 +32,8 @@ export interface OAuth2Code {
|
||||
client_id: string | number;
|
||||
scope: string;
|
||||
nonce?: string;
|
||||
code_challenge_method?: 'plain' | 'S256';
|
||||
code_challenge?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +182,9 @@ export interface OAuth2CodeAdapter {
|
||||
clientId: string | number,
|
||||
scope: string | string[],
|
||||
ttl: number,
|
||||
nonce?: string
|
||||
nonce?: string,
|
||||
code_challenge?: string,
|
||||
code_challenge_method?: 'plain' | 'S256',
|
||||
) => Promise<string>;
|
||||
|
||||
/**
|
||||
@ -212,6 +216,11 @@ export interface OAuth2CodeAdapter {
|
||||
* Check the time-to-live value
|
||||
*/
|
||||
checkTTL: (code: OAuth2Code) => boolean;
|
||||
|
||||
/**
|
||||
* Get PCKE info
|
||||
*/
|
||||
getCodeChallenge?: (code: OAuth2Code) => { method: 'plain' | 'S256'; challenge: string; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
13
src/utils/crypto-tools.ts
Normal file
13
src/utils/crypto-tools.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { timingSafeEqual, createHash } from 'crypto';
|
||||
|
||||
export function safeCompare(token: string, token2: string) {
|
||||
return timingSafeEqual(Buffer.from(token), Buffer.from(token2));
|
||||
}
|
||||
|
||||
export function sha256hash(input: string) {
|
||||
return createHash('sha256').update(input).digest();
|
||||
}
|
||||
|
||||
export function createS256(input: string) {
|
||||
return sha256hash(Buffer.from(input).toString('ascii')).toString('base64');
|
||||
}
|
Reference in New Issue
Block a user