Skip to content

Commit

Permalink
feat: update KindeUser decorator to fetch user profile using the acce…
Browse files Browse the repository at this point in the history
…ss token from cookies
  • Loading branch information
AmineYagoub committed May 26, 2024
1 parent 62298ac commit e7741f0
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 24 deletions.
22 changes: 18 additions & 4 deletions src/decorators/user.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import * as cookie from 'cookie';
import { IKindeUser } from '../lib/kinde.interface';
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { getEnvSafely } from '../lib/kinde.factory';
import { KINDE_ACCESS_TOKEN, KINDE_DOMAIN_URL } from '../lib/kinde.constant';
import axios from 'axios';

export const KindeUser = createParamDecorator(
(data: keyof IKindeUser, ctx: ExecutionContext) => {
async (data: keyof IKindeUser, ctx: ExecutionContext) => {
try {
const request = ctx.switchToHttp().getRequest();
const cookies = cookie.parse(request.headers.cookie || '');
if (!cookies['user']) return null;
const user = JSON.parse(cookies['user']) as IKindeUser;
return data ? user?.[data] : user;
const token = cookies[KINDE_ACCESS_TOKEN] ?? null;
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
};
const profile = await axios.get(
`${getEnvSafely(KINDE_DOMAIN_URL)}/oauth2/user_profile`,
{
headers,
},
);
if (profile.status === 200) {
return profile.data;
}
} catch (error) {
throw new Error('Error getting user');
}
Expand Down
17 changes: 4 additions & 13 deletions src/guards/abstract.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@ import jwt from 'jsonwebtoken';
import JwksClient from 'jwks-rsa';
import { KindePayload } from '../lib/kinde.interface';
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { getEnvSafely } from '../lib/kinde.factory';
import { KINDE_DOMAIN_URL } from '../lib/kinde.constant';

type TokenCallback = (err: Error | null, key?: string) => void;

const getEnvSafely = (envKey: string) => {
const envVal = process.env[envKey];
if (!envVal) throw new Error(`Missing env variable ${envKey}!`);
return envVal;
};

export abstract class AbstractGuard implements CanActivate {
private readonly AUD: string;
constructor() {
this.AUD = getEnvSafely('KINDE_AUDIENCE');
}

/**
* Determines if the user is authorized to access a route.
* @param context - The execution context of the request.
Expand All @@ -31,7 +22,7 @@ export abstract class AbstractGuard implements CanActivate {
*/
private getKey(header: jwt.JwtHeader, callback: TokenCallback) {
const client = JwksClient({
jwksUri: `${getEnvSafely('KINDE_DOMAIN_URL')}/.well-known/jwks`,
jwksUri: `${getEnvSafely(KINDE_DOMAIN_URL)}/.well-known/jwks`,
});
client.getSigningKey(header.kid, function (err, key) {
callback(err, key?.getPublicKey());
Expand All @@ -46,7 +37,7 @@ export abstract class AbstractGuard implements CanActivate {
protected verifyToken(token?: string): Promise<KindePayload> {
return new Promise((resolve, reject) => {
if (!token) return reject(new Error('No JWT token provided!'));
jwt.verify(token, this.getKey, { audience: this.AUD }, (err, decoded) => {
jwt.verify(token, this.getKey, {}, (err, decoded) => {
if (err) reject(err);
resolve(decoded as KindePayload);
});
Expand Down
3 changes: 2 additions & 1 deletion src/guards/isAuth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as cookie from 'cookie';
import { Reflector } from '@nestjs/core';
import { AbstractGuard } from './abstract.guard';
import { KindeIsAuth } from '../decorators/auth.decorator';
import { KINDE_ACCESS_TOKEN } from '../lib/kinde.constant';

@Injectable()
export class IsAuthGuard extends AbstractGuard {
Expand All @@ -22,7 +23,7 @@ export class IsAuthGuard extends AbstractGuard {
}
const request = context.switchToHttp().getRequest();
const cookies = cookie.parse(request.headers.cookie || '');
const decoded = await this.verifyToken(cookies['access_token']);
const decoded = await this.verifyToken(cookies[KINDE_ACCESS_TOKEN]);
if (!decoded) {
throw new UnauthorizedException();
}
Expand Down
3 changes: 2 additions & 1 deletion src/guards/permissions.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as cookie from 'cookie';
import { Reflector } from '@nestjs/core';
import { AbstractGuard } from './abstract.guard';
import { KindePermissions } from '../decorators/permissions.decorator';
import { KINDE_ACCESS_TOKEN } from '../lib/kinde.constant';

@Injectable()
export class PermissionsGuard extends AbstractGuard {
Expand All @@ -25,7 +26,7 @@ export class PermissionsGuard extends AbstractGuard {
}
const request = context.switchToHttp().getRequest();
const cookies = cookie.parse(request.headers.cookie || '');
const decoded = await this.verifyToken(cookies['access_token']);
const decoded = await this.verifyToken(cookies[KINDE_ACCESS_TOKEN]);
if (!decoded) {
throw new UnauthorizedException();
}
Expand Down
3 changes: 2 additions & 1 deletion src/guards/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as cookie from 'cookie';
import { Reflector } from '@nestjs/core';
import { AbstractGuard } from './abstract.guard';
import { KindeRoles } from '../decorators/roles.decorator';
import { KINDE_ACCESS_TOKEN } from '../lib/kinde.constant';

@Injectable()
export class RolesGuard extends AbstractGuard {
Expand All @@ -22,7 +23,7 @@ export class RolesGuard extends AbstractGuard {
}
const request = context.switchToHttp().getRequest();
const cookies = cookie.parse(request.headers.cookie || '');
const decoded = await this.verifyToken(cookies['access_token']);
const decoded = await this.verifyToken(cookies[KINDE_ACCESS_TOKEN]);
if (!decoded) {
throw new UnauthorizedException();
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/kinde.constant.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const KINDE_MODULE_OPTIONS = 'KINDE_MODULE_OPTIONS';
export const KINDE_DOMAIN_URL = 'KINDE_DOMAIN_URL';
export const KINDE_ACCESS_TOKEN = 'access_token';
5 changes: 5 additions & 0 deletions src/lib/kinde.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ import { KINDE_MODULE_OPTIONS } from './kinde.constant';
export function createKindeProvider(options: KindeModuleOptions): Provider[] {
return [{ provide: KINDE_MODULE_OPTIONS, useValue: options || {} }];
}
export const getEnvSafely = (envKey: string) => {
const envVal = process.env[envKey];
if (!envVal) throw new Error(`Missing env variable ${envKey}!`);
return envVal;
};
10 changes: 6 additions & 4 deletions src/lib/kinde.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ export interface HasuraRolesEntity {
}

export type IKindeUser = {
family_name: string | null;
given_name: string | null;
picture: string | null;
email: string;
id: string;
preferred_email: string;
username: string;
provided_id: string;
last_name: string;
first_name: string;
picture: string;
};

0 comments on commit e7741f0

Please sign in to comment.