Implemented SuperTokens
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, Session, UseGuards } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { SessionContainer } from "supertokens-node/recipe/session";
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
@@ -9,4 +11,11 @@ export class AppController {
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
|
||||
@Get('test')
|
||||
@UseGuards(AuthGuard)
|
||||
async getTest(@Session() session: SessionContainer): Promise<string> {
|
||||
// TODO: magic
|
||||
return "magic";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
AuthModule.forRoot({
|
||||
connectionURI: process.env.ST_API_URL,
|
||||
apiKey: process.env.ST_API_KEY,
|
||||
appInfo: {
|
||||
appName: process.env.APP_NAME,
|
||||
apiDomain: process.env.APP_URL,
|
||||
websiteDomain: process.env.WEBAPP_URL,
|
||||
apiBasePath: '/auth/api',
|
||||
websiteBasePath: '/auth',
|
||||
},
|
||||
}),
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
31
backend/src/auth.guard.ts
Normal file
31
backend/src/auth.guard.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Error as STError } from 'supertokens-node';
|
||||
|
||||
import { verifySession } from 'supertokens-node/recipe/session/framework/express';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const ctx = context.switchToHttp();
|
||||
|
||||
let err = undefined;
|
||||
const resp = ctx.getResponse();
|
||||
// You can create an optional version of this by passing {sessionRequired: false} to verifySession
|
||||
await verifySession()(ctx.getRequest(), resp, (res) => {
|
||||
err = res;
|
||||
});
|
||||
|
||||
if (resp.headersSent) {
|
||||
throw new STError({
|
||||
message: 'RESPONSE_SENT',
|
||||
type: 'RESPONSE_SENT',
|
||||
});
|
||||
}
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
29
backend/src/auth/auth.filter.ts
Normal file
29
backend/src/auth/auth.filter.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
|
||||
import { errorHandler } from 'supertokens-node/framework/express';
|
||||
import { Error as STError } from 'supertokens-node';
|
||||
import { Request, Response, NextFunction, ErrorRequestHandler } from 'express';
|
||||
|
||||
|
||||
@Catch(STError)
|
||||
export class SupertokensExceptionFilter implements ExceptionFilter {
|
||||
handler: ErrorRequestHandler;
|
||||
constructor() {
|
||||
this.handler = errorHandler();
|
||||
}
|
||||
catch(exception: Error, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
|
||||
const resp = ctx.getResponse<Response>();
|
||||
if (resp.headersSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handler(
|
||||
exception,
|
||||
ctx.getRequest<Request>(),
|
||||
resp,
|
||||
ctx.getNext<NextFunction>(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
15
backend/src/auth/auth.middleware.ts
Normal file
15
backend/src/auth/auth.middleware.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { middleware } from 'supertokens-node/framework/express';
|
||||
|
||||
@Injectable()
|
||||
export class AuthMiddleware implements NestMiddleware {
|
||||
supertokensMiddleware: any;
|
||||
|
||||
constructor() {
|
||||
this.supertokensMiddleware = middleware();
|
||||
}
|
||||
|
||||
use(req: Request, res: any, next: () => void) {
|
||||
return this.supertokensMiddleware(req, res, next);
|
||||
}
|
||||
}
|
||||
43
backend/src/auth/auth.module.ts
Normal file
43
backend/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
DynamicModule,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestModule,
|
||||
} from '@nestjs/common';
|
||||
import { AuthMiddleware } from './auth.middleware';
|
||||
import { AuthModuleConfig, ConfigInjectionToken } from './config.interface';
|
||||
import { SupertokensService } from './supertokens/supertokens.service';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
})
|
||||
export class AuthModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(AuthMiddleware).forRoutes('*');
|
||||
}
|
||||
|
||||
static forRoot({
|
||||
connectionURI,
|
||||
apiKey,
|
||||
appInfo,
|
||||
}: AuthModuleConfig): DynamicModule {
|
||||
return {
|
||||
providers: [
|
||||
{
|
||||
useValue: {
|
||||
appInfo,
|
||||
connectionURI,
|
||||
apiKey,
|
||||
},
|
||||
provide: ConfigInjectionToken,
|
||||
},
|
||||
SupertokensService,
|
||||
],
|
||||
exports: [],
|
||||
imports: [],
|
||||
module: AuthModule,
|
||||
};
|
||||
}
|
||||
}
|
||||
9
backend/src/auth/config.interface.ts
Normal file
9
backend/src/auth/config.interface.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { AppInfo } from 'supertokens-node/types';
|
||||
|
||||
export const ConfigInjectionToken = 'ConfigInjectionToken';
|
||||
|
||||
export type AuthModuleConfig = {
|
||||
appInfo: AppInfo;
|
||||
connectionURI: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
8
backend/src/auth/session.decorator.ts
Normal file
8
backend/src/auth/session.decorator.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
|
||||
export const Session = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
return request.session;
|
||||
},
|
||||
);
|
||||
48
backend/src/auth/supertokens/supertokens.service.ts
Normal file
48
backend/src/auth/supertokens/supertokens.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AuthModuleConfig, ConfigInjectionToken } from '../config.interface';
|
||||
import supertokens from 'supertokens-node';
|
||||
import ThirdPartyEmailPassword from 'supertokens-node/recipe/thirdpartyemailpassword';
|
||||
import Session from 'supertokens-node/recipe/session';
|
||||
|
||||
@Injectable()
|
||||
export class SupertokensService {
|
||||
constructor(@Inject(ConfigInjectionToken) private config: AuthModuleConfig) {
|
||||
supertokens.init({
|
||||
appInfo: config.appInfo,
|
||||
supertokens: {
|
||||
connectionURI: config.connectionURI,
|
||||
apiKey: config.apiKey,
|
||||
},
|
||||
recipeList: [
|
||||
ThirdPartyEmailPassword.init({
|
||||
providers: [
|
||||
ThirdPartyEmailPassword.Google({
|
||||
clientId:
|
||||
'1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com',
|
||||
clientSecret: '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',
|
||||
}),
|
||||
ThirdPartyEmailPassword.Github({
|
||||
clientId: '467101b197249757c71f',
|
||||
clientSecret: 'e97051221f4b6426e8fe8d51486396703012f5bd',
|
||||
}),
|
||||
ThirdPartyEmailPassword.Apple({
|
||||
clientId: '4398792-io.supertokens.example.service',
|
||||
clientSecret: {
|
||||
keyId: '7M48Y4RYDL',
|
||||
privateKey:
|
||||
'-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----',
|
||||
teamId: 'YWQCXGJRJL',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Session.init({
|
||||
jwt: {
|
||||
enable: true,
|
||||
issuer: `${process.env.APP_URL}/auth/api`,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import supertokens from 'supertokens-node';
|
||||
import { SupertokensExceptionFilter } from './auth/auth.filter';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(3000);
|
||||
app.enableCors({
|
||||
origin: [process.env.WEBAPP_URL],
|
||||
allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
|
||||
credentials: true,
|
||||
});
|
||||
app.useGlobalFilters(new SupertokensExceptionFilter());
|
||||
|
||||
await app.listen(process.env.APP_PORT);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
Reference in New Issue
Block a user