Skip to content

Commit

Permalink
feat(nestjs-clerk): initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
y-nk committed Jan 15, 2025
1 parent 69b8cac commit ee58eaa
Show file tree
Hide file tree
Showing 10 changed files with 2,265 additions and 0 deletions.
2,040 changes: 2,040 additions & 0 deletions nestjs-clerk/package-lock.json

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions nestjs-clerk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@y_nk/nestjs-clerk",
"version": "1.0.0",
"description": "auth and permission guards using clerk for nestjs",
"homepage": "https://github.com/y-nk/nonorepo/tree/main/astro-components",
"keywords": [
"nestjs",
"clerk",
"auth"
],
"author": "Julien Barbay <[email protected]>",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"deploy": "npm publish --access public"
},
"devDependencies": {
"@clerk/express": "^1.3.34",
"@nestjs/common": "^10.4.15",
"@nestjs/core": "^10.4.15",
"rimraf": "^6.0.1",
"typescript": "^5.7.3"
},
"peerDependencies": {
"@clerk/express": "^1.3.34",
"@nestjs/common": "^10.4.15",
"@nestjs/core": "^10.4.15"
}
}
28 changes: 28 additions & 0 deletions nestjs-clerk/src/acl.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getAuth } from "@clerk/express";
import {
ForbiddenException,
Injectable,
type CanActivate,
type ExecutionContext,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";

@Injectable()
export class ClerkAclGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext) {
const permission = this.reflector.get<string | undefined>(
"Acl",
context.getHandler(),
);

const req = context.switchToHttp().getRequest();
const auth = getAuth(req);

if (!permission || !auth.userId) return true;

if (!auth.has({ permission })) throw new ForbiddenException();

return true;
}
}
41 changes: 41 additions & 0 deletions nestjs-clerk/src/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { clerkMiddleware, getAuth } from "@clerk/express";
import {
Inject,
Injectable,
UnauthorizedException,
type CanActivate,
type ExecutionContext,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";

import { CLERK_CONFIG } from "./tokens";
import type { ClerkConfig } from "./types";

@Injectable()
export class ClerkAuthGuard implements CanActivate {
constructor(
private reflector: Reflector,
@Inject(CLERK_CONFIG) private clerkConfig: ClerkConfig,
) {}
async canActivate(context: ExecutionContext) {
const noAuth = [context.getHandler(), context.getClass()].some((target) =>
this.reflector.get<boolean>("NoAuth", target),
);

if (context.getType() !== "http" || noAuth) return true;

const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
const middleware = clerkMiddleware(this.clerkConfig);

const error = await new Promise<Error | undefined>((resolve) => {
middleware(req, res, resolve);
});

const auth = getAuth(req);

if (error || !auth.userId) throw new UnauthorizedException();

return true;
}
}
22 changes: 22 additions & 0 deletions nestjs-clerk/src/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getAuth } from "@clerk/express";
import {
createParamDecorator,
SetMetadata,
type CustomDecorator,
type ExecutionContext,
} from "@nestjs/common";

export function NoAuth(): CustomDecorator {
return SetMetadata("NoAuth", true);
}

export function Acl(permission: string): CustomDecorator {
return SetMetadata("Acl", permission);
}

export const Clerk = createParamDecorator(
(_: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
return getAuth(req);
},
);
6 changes: 6 additions & 0 deletions nestjs-clerk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./acl.guard";
export * from "./auth.guard";
export * from "./decorators";
export * from "./module";
export * from "./tokens";
export * from "./types";
56 changes: 56 additions & 0 deletions nestjs-clerk/src/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { clerkClient } from "@clerk/express";
import { Module, type Provider } from "@nestjs/common";
import { APP_GUARD } from "@nestjs/core";

import { ClerkAclGuard } from "./acl.guard";
import { ClerkAuthGuard } from "./auth.guard";
import { CLERK_CLIENT, CLERK_CONFIG } from "./tokens";
import type { ClerkConfig } from "./types";

const providers: Provider[] = [
ClerkAuthGuard,
ClerkAclGuard,
{
provide: APP_GUARD,
useExisting: ClerkAuthGuard,
},
{
provide: APP_GUARD,
useExisting: ClerkAclGuard,
},
{
provide: CLERK_CLIENT,
useValue: clerkClient,
},
];

@Module({})
export class ClerkModule {
static register(clerkConfig?: ClerkConfig) {
return {
module: ClerkModule,
providers: [
{
provide: CLERK_CONFIG,
useValue: clerkConfig ?? {},
},
...providers,
],
exports: [ClerkAuthGuard, ClerkAclGuard],
};
}

static async registerAsync(provider: Provider<ClerkConfig>) {
return {
module: ClerkModule,
providers: [
{
provide: CLERK_CONFIG,
...provider,
},
...providers,
],
exports: [ClerkAuthGuard, ClerkAclGuard],
};
}
}
2 changes: 2 additions & 0 deletions nestjs-clerk/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CLERK_CONFIG = "CLERK_CONFIG";
export const CLERK_CLIENT = "CLERK_CLIENT";
6 changes: 6 additions & 0 deletions nestjs-clerk/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { AuthObject, clerkMiddleware } from "@clerk/express";

export type ClerkConfig = Parameters<typeof clerkMiddleware>[0];

// & is to enforce the discriminating union since the type is not available at @clerk/express
export type ClerkAuth = AuthObject & { sessionId: string };
32 changes: 32 additions & 0 deletions nestjs-clerk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

"module": "nodenext",
"moduleResolution": "nodenext",
"lib": ["esnext"],
"target": "esnext",

"allowUnreachableCode": false,
"alwaysStrict": true,
"declaration": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noImplicitThis": false,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"strictPropertyInitialization": false
},
"exclude": ["node_modules", "dist"]
}

0 comments on commit ee58eaa

Please sign in to comment.