Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Auth with SIWE + Permissioned Area i.e. Dashboard #149

Merged
merged 16 commits into from
Mar 15, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CREATE TYPE "TransactionType" AS ENUM ('CREDIT', 'DEBIT');

-- CreateTable
CREATE TABLE "Org" (
"id" VARCHAR(8) NOT NULL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require a reset of the db right? Fine here, but we should really start getting into better habits re: migration.

"id" TEXT NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"deletedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand All @@ -15,7 +15,7 @@ CREATE TABLE "Org" (

-- CreateTable
CREATE TABLE "Enterprise" (
"id" VARCHAR(8) NOT NULL,
"id" TEXT NOT NULL,
"deletedAt" TIMESTAMP(3),
"enabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down Expand Up @@ -51,7 +51,7 @@ CREATE TABLE "User" (

-- CreateTable
CREATE TABLE "App" (
"id" VARCHAR(8) NOT NULL,
"id" TEXT NOT NULL,
"deletedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
Expand Down Expand Up @@ -118,7 +118,7 @@ CREATE TABLE "RelayLedger" (

-- CreateTable
CREATE TABLE "_OrgToUser" (
"A" VARCHAR(8) NOT NULL,
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);

Expand Down
6 changes: 3 additions & 3 deletions services/postgres/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ generator client {
}

model Org {
id String @id @unique @default(cuid()) @db.VarChar(8)
id String @id @unique @default(cuid())
users User[]
active Boolean @default(true)
deletedAt DateTime?
Expand All @@ -21,7 +21,7 @@ model Org {
}

model Enterprise {
id String @id @unique @default(cuid()) @db.VarChar(8)
id String @id @unique @default(cuid())
orgs Org[]
tenants Tenant[]
deletedAt DateTime?
Expand Down Expand Up @@ -55,7 +55,7 @@ model User {
}

model App {
id String @id @unique @default(cuid()) @db.VarChar(8)
id String @id() @unique @default(cuid())
appRules AppRule[]
deletedAt DateTime?
createdAt DateTime @default(now())
Expand Down
7 changes: 6 additions & 1 deletion web-portal/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { PrismaClient } from '@/.generated/client';
import { TenantModule } from './tenant/tenant.module';
import { AppController } from './app.controller';
import { SiweModule } from './siwe/siwe.module';
import { UserModule } from './user/user.module';
import { AppsModule } from './apps/apps.module';
import { OrgModule } from './org/org.module';

@Module({
imports: [
Expand All @@ -18,8 +21,10 @@ import { SiweModule } from './siwe/siwe.module';
envFilePath: ['@/.env', '.env.local'],
}),
TenantModule,

SiweModule,
UserModule,
AppsModule,
OrgModule,
],
providers: [AppService],
controllers: [AppController],
Expand Down
18 changes: 18 additions & 0 deletions web-portal/backend/src/apps/apps.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppsController } from './apps.controller';

describe('AppsController', () => {
let controller: AppsController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AppsController],
}).compile();

controller = module.get<AppsController>(AppsController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
19 changes: 19 additions & 0 deletions web-portal/backend/src/apps/apps.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller, Get, Param, Post } from '@nestjs/common';
import { AppsService } from './apps.service';

@Controller('apps')
export class AppsController {
constructor(private readonly appsService: AppsService) {}

@Get(':userAddress')
async getUserApps(@Param('userAddress') userAddress: string) {
// @note: This action fetches apps by tenant;
return this.appsService.getAppsByUser(userAddress);
}

@Post(':userAddress')
async createApp(@Param('userAddress') userAddress: string) {
// @note: This action creates app for tenant by enterpriseId;
return this.appsService.createApp(userAddress);
}
}
11 changes: 11 additions & 0 deletions web-portal/backend/src/apps/apps.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AppsController } from './apps.controller';
import { AppsService } from './apps.service';
import { UserService } from '../user/user.service';
import { TenantService } from '../tenant/tenant.service';

@Module({
controllers: [AppsController],
providers: [AppsService, UserService, TenantService],
})
export class AppsModule {}
18 changes: 18 additions & 0 deletions web-portal/backend/src/apps/apps.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppsService } from './apps.service';

describe('AppsService', () => {
let service: AppsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AppsService],
}).compile();

service = module.get<AppsService>(AppsService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
70 changes: 70 additions & 0 deletions web-portal/backend/src/apps/apps.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Injectable, Inject, HttpException, HttpStatus } from '@nestjs/common';
import { CustomPrismaService } from 'nestjs-prisma';
import { PrismaClient } from '@/.generated/client';
import { UserService } from '../user/user.service';
@Injectable()
export class AppsService {
constructor(
@Inject('Postgres')
private prisma: CustomPrismaService<PrismaClient>, // <-- Inject the PrismaClient
private userService: UserService,
) {}

async getTenantsByUser(userAddress: string) {
const user = await this.userService.getOrCreate(userAddress);

const enterprises = user.orgs.map((org) => org.enterpriseId);

const tenants = await this.prisma.client.tenant.findMany({
where: {
enterpriseId: {
in: enterprises,
},
},
});

if (!tenants || tenants.length === 0) {
throw new HttpException('No tenants found', HttpStatus.NOT_FOUND);
}
return tenants;
}

async getAppsByUser(userAddress: string) {
const tenants = await this.getTenantsByUser(userAddress);
const apps = await this.prisma.client.app.findMany({
where: {
tenantId: {
in: tenants.map((tenant) => tenant.id),
},
},
include: {
appRules: true,
},
});

if (!apps || apps?.length === 0) {
throw new HttpException('No apps found', HttpStatus.NOT_FOUND);
}
return apps;
}

async createApp(userAddress: string) {
const tenants = await this.getTenantsByUser(userAddress);

if (!tenants) return;
const newApp = await this.prisma.client.app.create({
data: {
tenantId: tenants[0].id,
},
});

if (!newApp) {
return new HttpException(
`Could not create app for this tenant`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}

return newApp;
}
}
18 changes: 18 additions & 0 deletions web-portal/backend/src/org/org.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OrgController } from './org.controller';

describe('OrgController', () => {
let controller: OrgController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [OrgController],
}).compile();

controller = module.get<OrgController>(OrgController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
4 changes: 4 additions & 0 deletions web-portal/backend/src/org/org.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';

@Controller('org')
export class OrgController {}
9 changes: 9 additions & 0 deletions web-portal/backend/src/org/org.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { OrgController } from './org.controller';
import { OrgService } from './org.service';

@Module({
controllers: [OrgController],
providers: [OrgService]
})
export class OrgModule {}
18 changes: 18 additions & 0 deletions web-portal/backend/src/org/org.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OrgService } from './org.service';

describe('OrgService', () => {
let service: OrgService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [OrgService],
}).compile();

service = module.get<OrgService>(OrgService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
24 changes: 24 additions & 0 deletions web-portal/backend/src/org/org.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable, Inject } from '@nestjs/common';
import { CustomPrismaService } from 'nestjs-prisma';
import { PrismaClient } from '@/.generated/client';

@Injectable()
export class OrgService {
constructor(
@Inject('Postgres')
private prisma: CustomPrismaService<PrismaClient>, // <-- Inject the PrismaClient
) {}

async getOrgsByUser(userId: string) {
const org = this.prisma.client.org.findMany({
where: {
users: {
every: {
id: userId,
},
},
},
});
return org;
}
}
19 changes: 14 additions & 5 deletions web-portal/backend/src/siwe/siwe.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
Req,
Res,
HttpStatus,
Delete,
} from '@nestjs/common';
import { SiweService } from './siwe.service';
import { Request, Response } from 'express';

@Controller('siwe')
export class SiweController {
constructor(private readonly siweService: SiweService) {}
Expand All @@ -18,9 +20,8 @@ export class SiweController {
@Req() request: Request,
@Res() response: Response,
): Promise<any> {
console.log('Get Session from cookie using siwe');
// @note: Get Session from cookie using siwe
const sessionCookie = request?.cookies['session'];

if (!sessionCookie) {
return response.status(HttpStatus.BAD_REQUEST).send(false);
}
Expand All @@ -36,7 +37,7 @@ export class SiweController {

@Post()
async verifyMessage(@Req() request: Request, @Res() response: Response) {
console.log('Verify ownership using siwe');
// @note: This actions is used to Verify ownership using siwe
const { message, signature } = request.body;

const nonceRegex = /Nonce: (\S+)/;
Expand All @@ -48,8 +49,6 @@ export class SiweController {
nonce,
});

console.log('authentication was ', authenticated);

return response
.status(authenticated ? HttpStatus.OK : HttpStatus.NOT_FOUND)
.cookie('session', authenticated, {
Expand All @@ -63,4 +62,14 @@ export class SiweController {
const nonce = this.siweService.getNonce();
return response.status(HttpStatus.OK).send(nonce);
}

@Delete()
async signOut(@Req() request: Request, @Res() response: Response) {
const sessionCookie = request?.cookies['session'];
if (sessionCookie) {
sessionCookie.destroy();
return response.status(HttpStatus.OK);
}
return response.status(HttpStatus.BAD_REQUEST);
}
}
5 changes: 4 additions & 1 deletion web-portal/backend/src/siwe/siwe.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Module } from '@nestjs/common';
import { SiweController } from './siwe.controller';
import { SiweService } from './siwe.service';
import { UserService } from '../user/user.service';
import { TenantService } from '../tenant/tenant.service';

@Module({
controllers: [SiweController],
providers: [SiweService],
providers: [SiweService, UserService, TenantService],
exports: [SiweService],
})
export class SiweModule {}
Loading