Skip to content

Commit

Permalink
Add logLevels option, closes #41
Browse files Browse the repository at this point in the history
  • Loading branch information
ferrerojosh committed Apr 12, 2021
1 parent 0041a36 commit 5b34b52
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
secret: 'secret',
// optional if you want to retrieve JWT from cookie
cookieKey: 'KEYCLOAK_JWT',
logLevel: ['warn']
}),
],
providers: [
Expand Down
1 change: 1 addition & 0 deletions example/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ProductModule } from './product/product.module';
secret: '05c1ff5e-f9ba-4622-98e3-c4c9d280546e',
// optional if you want to retrieve JWT from cookie
cookieKey: 'KEYCLOAK_JWT',
logLevels: ['verbose']
}),
ProductModule,
],
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const KEYCLOAK_CONNECT_OPTIONS = 'KEYCLOAK_CONNECT_OPTIONS';
* Key for injecting a keycloak instance.
*/
export const KEYCLOAK_INSTANCE = 'KEYCLOAK_INSTANCE';

/**
* Key for injecting the nest keycloak logger.
*/
export const KEYCLOAK_LOGGER = 'KEYCLOAK_LOGGER';
19 changes: 17 additions & 2 deletions src/guards/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import * as KeycloakConnect from 'keycloak-connect';
import { KEYCLOAK_CONNECT_OPTIONS, KEYCLOAK_INSTANCE } from '../constants';
import { KeycloakLogger } from '../logger';
import {
KEYCLOAK_CONNECT_OPTIONS,
KEYCLOAK_INSTANCE,
KEYCLOAK_LOGGER,
} from '../constants';
import {
META_SKIP_AUTH,
META_UNPROTECTED,
Expand All @@ -26,6 +31,8 @@ export class AuthGuard implements CanActivate {
private keycloak: KeycloakConnect.Keycloak,
@Inject(KEYCLOAK_CONNECT_OPTIONS)
private keycloakOpts: KeycloakConnectOptions,
@Inject(KEYCLOAK_LOGGER)
private logger: KeycloakLogger,
private readonly reflector: Reflector,
) {}

Expand Down Expand Up @@ -53,9 +60,12 @@ export class AuthGuard implements CanActivate {

// No jwt token given, immediate return
if (isInvalidJwt) {
this.logger.verbose('Invalid JWT, unauthorized');
throw new UnauthorizedException();
}

this.logger.verbose(`User JWT: ${jwt}`);

try {
const result = await this.keycloak.grantManager.validateAccessToken(jwt);

Expand All @@ -64,10 +74,14 @@ export class AuthGuard implements CanActivate {
request.user = await this.keycloak.grantManager.userInfo(jwt);
// Attach raw access token JWT extracted from bearer/cookie
request.accessTokenJWT = jwt;

this.logger.verbose(
`Authenticated User: ${JSON.stringify(request.user)}`,
);
return true;
}
} catch (ex) {
console.error(`Cannot validate access token: `, ex);
this.logger.warn(`Cannot validate access token: ${ex}`);
}

throw new UnauthorizedException();
Expand All @@ -82,6 +96,7 @@ export class AuthGuard implements CanActivate {

// We only allow bearer
if (auth[0].toLowerCase() !== 'bearer') {
this.logger.verbose(`No bearer header, unauthorized`);
throw new UnauthorizedException();
}

Expand Down
23 changes: 14 additions & 9 deletions src/guards/resource.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import * as KeycloakConnect from 'keycloak-connect';
import { KEYCLOAK_INSTANCE } from '../constants';
import { KeycloakLogger } from '../logger';
import { KEYCLOAK_INSTANCE, KEYCLOAK_LOGGER } from '../constants';
import { META_RESOURCE } from '../decorators/resource.decorator';
import { META_SCOPES } from '../decorators/scopes.decorator';
import { extractRequest } from '../util';
Expand All @@ -19,11 +20,11 @@ import { extractRequest } from '../util';
*/
@Injectable()
export class ResourceGuard implements CanActivate {
logger = new Logger(ResourceGuard.name);

constructor(
@Inject(KEYCLOAK_INSTANCE)
private keycloak: KeycloakConnect.Keycloak,
@Inject(KEYCLOAK_LOGGER)
private logger: KeycloakLogger,
private readonly reflector: Reflector,
) {}

Expand All @@ -39,18 +40,22 @@ export class ResourceGuard implements CanActivate {

// No resource given, since we are permissive, allow
if (!resource) {
this.logger.verbose(
`Controller has no @Resource defined, request allowed`,
);
return true;
}

this.logger.verbose(
`Protecting resource '${resource}' with scopes: [ ${scopes} ]`,
);

// No scopes given, since we are permissive, allow
if (!scopes) {
this.logger.verbose(`Route has no @Scope defined, request allowed`);
return true;
}

this.logger.verbose(
`Protecting resource [ ${resource} ] with scopes: [ ${scopes} ]`,
);

// Build permissions
const permissions = scopes.map(scope => `${resource}:${scope}`);
// Extract request/response
Expand All @@ -63,9 +68,9 @@ export class ResourceGuard implements CanActivate {

// If statement for verbose logging only
if (!isAllowed) {
this.logger.verbose(`Resource '${resource}' denied to ${user}.`);
this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ]`);
} else {
this.logger.verbose(`Resource '${resource}' granted to ${user}.`);
this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ]`);
}

return isAllowed;
Expand Down
7 changes: 6 additions & 1 deletion src/guards/role.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
import { Reflector } from '@nestjs/core';
import * as KeycloakConnect from 'keycloak-connect';
import { extractRequest } from '../util';
import { KEYCLOAK_INSTANCE } from '../constants';
import { KEYCLOAK_INSTANCE, KEYCLOAK_LOGGER } from '../constants';
import { META_ALLOW_ANY_ROLE } from '../decorators/allow-any-role.decorator';
import { META_ROLES } from '../decorators/roles.decorator';
import { KeycloakLogger } from '../logger';

/**
* A permissive type of role guard. Roles are set via `@Roles` decorator.
Expand All @@ -21,6 +22,8 @@ export class RoleGuard implements CanActivate {
constructor(
@Inject(KEYCLOAK_INSTANCE)
private keycloak: KeycloakConnect.Keycloak,
@Inject(KEYCLOAK_LOGGER)
private logger: KeycloakLogger,
private readonly reflector: Reflector,
) {}

Expand All @@ -39,6 +42,8 @@ export class RoleGuard implements CanActivate {
return true;
}

this.logger.verbose(`Roles: `, JSON.stringify(roles));

// Extract request
const [request] = extractRequest(context);
const { accessTokenJWT } = request;
Expand Down
7 changes: 7 additions & 0 deletions src/interface/keycloak-connect-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// The typings are a bit of a mess, I'm sure there's a better way to do this.

import { LogLevel } from '@nestjs/common';

/**
* Keycloak Connect options.
*/
Expand All @@ -11,6 +13,11 @@ export interface KeycloakConnectOptions {
*/
cookieKey?: string;

/**
* Log level.
*/
logLevels?: LogLevel[];

// Keycloak options
// https://github.com/keycloak/keycloak-nodejs-connect/blob/f8e011aea5/middleware/auth-utils/config.js

Expand Down
31 changes: 22 additions & 9 deletions src/keycloak-connect.module.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { DynamicModule, Module, Provider } from '@nestjs/common';
import * as KeycloakConnect from 'keycloak-connect';

import { KEYCLOAK_CONNECT_OPTIONS, KEYCLOAK_INSTANCE } from './constants';
import {
KEYCLOAK_CONNECT_OPTIONS,
KEYCLOAK_INSTANCE,
KEYCLOAK_LOGGER,
} from './constants';
import { KeycloakConnectModuleAsyncOptions } from './interface/keycloak-connect-module-async-options.interface';
import { KeycloakConnectOptionsFactory } from './interface/keycloak-connect-options-factory.interface';
import { KeycloakConnectOptions } from './interface/keycloak-connect-options.interface';
import { KeycloakLogger } from './logger';

export * from './constants';
export * from './decorators/allow-any-role.decorator';
export * from './decorators/authenticated-user.decorator';
export * from './decorators/enforcer-options.decorator';
export * from './decorators/resource.decorator';
export * from './decorators/scopes.decorator';
export * from './decorators/roles.decorator';
export * from './decorators/allow-any-role.decorator';
export * from './decorators/scopes.decorator';
export * from './decorators/unprotected.decorator';
export * from './decorators/enforcer-options.decorator';
export * from './decorators/authenticated-user.decorator';
export * from './guards/auth.guard';
export * from './guards/resource.guard';
export * from './guards/role.guard';
export * from './constants';

@Module({})
export class KeycloakConnectModule {
Expand All @@ -28,8 +32,8 @@ export class KeycloakConnectModule {

return {
module: KeycloakConnectModule,
providers: [optsProvider, this.keycloakProvider],
exports: [optsProvider, this.keycloakProvider],
providers: [optsProvider, this.loggerProvider, this.keycloakProvider],
exports: [optsProvider, this.loggerProvider, this.keycloakProvider],
};
}

Expand Down Expand Up @@ -81,6 +85,15 @@ export class KeycloakConnectModule {
};
}

private static loggerProvider: Provider = {
provide: KEYCLOAK_LOGGER,
useFactory: (opts: KeycloakConnectOptions) => {
const logger = new KeycloakLogger(opts.logLevels);
return logger;
},
inject: [KEYCLOAK_CONNECT_OPTIONS],
};

private static keycloakProvider: Provider = {
provide: KEYCLOAK_INSTANCE,
useFactory: (opts: KeycloakConnectOptions) => {
Expand Down
47 changes: 47 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Logger, LoggerService, LogLevel } from '@nestjs/common';

/**
* Wrapper for Nest Logger, since the new ConsoleLogger will only arrive on Nest 8.0
*/
export class KeycloakLogger implements LoggerService {
private logger = new Logger('Keycloak');

constructor(private readonly logLevels: LogLevel[]) {}

log(message: any, context?: string) {
this.callWrapped('log', message, context);
}

error(message: any, trace?: string, context?: string) {
this.callWrapped('error', message, context);
}

warn(message: any, context?: string) {
this.callWrapped('warn', message, context);
}

debug?(message: any, context?: string) {
this.callWrapped('debug', message, context);
}

verbose?(message: any, context?: string) {
this.callWrapped('verbose', message, context);
}

private isLogLevelEnabled(level: LogLevel): boolean {
return this.logLevels.includes(level);
}

private callWrapped(
name: 'log' | 'warn' | 'debug' | 'verbose' | 'error',
message: any,
context?: string,
) {
if (!this.isLogLevelEnabled(name)) {
return;
}
const func = this.logger[name];

func && func.call(this.logger, message, context);
}
}
1 change: 1 addition & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ContextType, ExecutionContext } from '@nestjs/common';
import { KeycloakLogger } from './logger';

type GqlContextType = 'graphql' | ContextType;

Expand Down

0 comments on commit 5b34b52

Please sign in to comment.