Re-implement authentication (#136)
* Remove hasura and hasura-auth * Implement authentication
This commit is contained in:
2
front/.env.example
Normal file
2
front/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_API_URL=http://localhost:3000/graphql
|
||||||
|
REACT_APP_AUTH_URL=http://localhost:3000/auth
|
||||||
@ -7,6 +7,7 @@ function Callback() {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
const refreshToken = searchParams.get('refreshToken');
|
const refreshToken = searchParams.get('refreshToken');
|
||||||
|
console.log('refreshToken', refreshToken);
|
||||||
localStorage.setItem('refreshToken', refreshToken || '');
|
localStorage.setItem('refreshToken', refreshToken || '');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,7 @@ function Login() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasAccessToken()) {
|
if (!hasAccessToken()) {
|
||||||
window.location.href =
|
window.location.href = process.env.REACT_APP_AUTH_URL + '/google' || '';
|
||||||
process.env.REACT_APP_AUTH_URL + '/signin/provider/google' || '';
|
|
||||||
} else {
|
} else {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1 @@
|
|||||||
HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://postgres:postgrespassword@postgres:5432/hasura
|
|
||||||
HASURA_GRAPHQL_PG_DATABASE_URL=postgres://postgres:postgrespassword@postgres:5432/default
|
|
||||||
HASURA_GRAPHQL_ADMIN_SECRET=secret
|
|
||||||
HASURA_GRAPHQL_JWT_SECRET='{"type":"HS256", "key": "jwt-very-long-hard-to-guess-secret"}'
|
|
||||||
HASURA_EVENT_HANDLER_URL=http://twenty-server:3000/hasura/events
|
|
||||||
|
|
||||||
HASURA_AUTH_SERVER_URL=http://localhost:4000
|
|
||||||
HASURA_AUTH_CLIENT_URL=http://localhost:3001/auth/callback
|
|
||||||
HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_ID=REPLACE_ME
|
|
||||||
HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_SECRET=REPLACE_ME
|
|
||||||
HASURA_AUTH_GRAPHQL_URL=http://twenty-hasura:8080/v1/graphql
|
|
||||||
|
|
||||||
FRONT_REACT_APP_API_URL=http://localhost:8080
|
|
||||||
FRONT_REACT_APP_AUTH_URL=http://localhost:4000
|
|
||||||
FRONT_HASURA_GRAPHQL_ENDPOINT=http://twenty-hasura:8080/v1/graphql
|
|
||||||
|
|
||||||
SERVER_HASURA_EVENT_HANDLER_SECRET_HEADER=secret
|
|
||||||
SERVER_DATABASE_URL=postgres://postgres:postgrespassword@postgres:5432/default
|
|
||||||
|
|
||||||
POSTGRES_PASSWORD=postgrespassword
|
POSTGRES_PASSWORD=postgrespassword
|
||||||
@ -7,9 +7,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
- "6006:6006"
|
- "6006:6006"
|
||||||
environment:
|
|
||||||
REACT_APP_API_URL: ${FRONT_REACT_APP_API_URL}
|
|
||||||
REACT_APP_AUTH_URL: ${FRONT_REACT_APP_AUTH_URL}
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../../front:/app/front
|
- ../../front:/app/front
|
||||||
- twenty_node_modules_front:/app/front/node_modules
|
- twenty_node_modules_front:/app/front/node_modules
|
||||||
@ -24,8 +21,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ../../server:/app/server
|
- ../../server:/app/server
|
||||||
- twenty_node_modules_server:/app/server/node_modules
|
- twenty_node_modules_server:/app/server/node_modules
|
||||||
environment:
|
|
||||||
SERVER_DATABASE_URL: ${SERVER_DATABASE_URL}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
twenty-docs:
|
twenty-docs:
|
||||||
|
|||||||
7
server/.env.example
Normal file
7
server/.env.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
AUTH_GOOGLE_CLIENT_ID=REPLACE_ME
|
||||||
|
AUTH_GOOGLE_SECRET=REPLACE_ME
|
||||||
|
AUTH_GOOGLE_CALLBACK_URL='http://localhost:3000/google/redirect'
|
||||||
|
JWT_SECRET=secret_jwt
|
||||||
|
JWT_EXPIRES_IN=300
|
||||||
|
SERVER_DATABASE_URL=postgres://postgres:postgrespassword@postgres:5432/default
|
||||||
|
FRONT_AUTH_CALLBACK_URL=http://localhost:3001/auth/callback
|
||||||
12622
server/package-lock.json
generated
12622
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,10 +23,12 @@
|
|||||||
"prisma:migrate": "npx prisma migrate deploy"
|
"prisma:migrate": "npx prisma migrate deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/apollo": "^11.0.5",
|
"@nestjs/apollo": "^10.0.5",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
|
"@nestjs/config": "^2.3.2",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/graphql": "^11.0.5",
|
"@nestjs/jwt": "^10.0.3",
|
||||||
|
"@nestjs/passport": "^9.0.3",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
"@nestjs/serve-static": "^3.0.0",
|
"@nestjs/serve-static": "^3.0.0",
|
||||||
"@nestjs/terminus": "^9.2.2",
|
"@nestjs/terminus": "^9.2.2",
|
||||||
@ -34,6 +36,10 @@
|
|||||||
"apollo-server-express": "^3.12.0",
|
"apollo-server-express": "^3.12.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"jest-mock-extended": "^3.0.4",
|
"jest-mock-extended": "^3.0.4",
|
||||||
|
"passport": "^0.6.0",
|
||||||
|
"passport-google-oauth20": "^2.0.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
@ -47,6 +53,7 @@
|
|||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "28.1.8",
|
"@types/jest": "28.1.8",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.0.0",
|
||||||
|
"@types/passport-google-oauth20": "^2.0.11",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
|
|||||||
@ -3,9 +3,17 @@ import { AppController } from './app.controller';
|
|||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { HealthController } from './health.controller';
|
import { HealthController } from './health.controller';
|
||||||
import { TerminusModule } from '@nestjs/terminus';
|
import { TerminusModule } from '@nestjs/terminus';
|
||||||
|
|
||||||
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { ApiModule } from './api/api.module';
|
import { ApiModule } from './api/api.module';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TerminusModule, ApiModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({}),
|
||||||
|
TerminusModule,
|
||||||
|
AuthModule,
|
||||||
|
ApiModule,
|
||||||
|
],
|
||||||
controllers: [AppController, HealthController],
|
controllers: [AppController, HealthController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|||||||
20
server/src/auth/auth.controller.ts
Normal file
20
server/src/auth/auth.controller.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Controller, Post, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { AuthService } from './services/auth.service';
|
||||||
|
|
||||||
|
@Controller('auth/token')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
generateAccessToken(@Req() req: Request, @Res() res: Response) {
|
||||||
|
const refreshToken = req.body.refreshToken;
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
return res.status(400).send('Refresh token not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send(this.authService.generateAccessToken(refreshToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
38
server/src/auth/auth.module.ts
Normal file
38
server/src/auth/auth.module.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||||
|
import { AuthService } from './services/auth.service';
|
||||||
|
import { GoogleAuthController } from './google.auth.controller';
|
||||||
|
import { GoogleStrategy } from './strategies/google.auth.strategy';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { UserRepository } from 'src/entities/user/user.repository';
|
||||||
|
import { WorkspaceRepository } from 'src/entities/workspace/workspace.repository';
|
||||||
|
import { RefreshTokenRepository } from 'src/entities/refresh-token/refresh-token.repository';
|
||||||
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [JwtModule.registerAsync({
|
||||||
|
useFactory: async (configService: ConfigService) => {
|
||||||
|
return {
|
||||||
|
secret: configService.get<string>('JWT_SECRET'),
|
||||||
|
signOptions: {
|
||||||
|
expiresIn: configService.get<string>('JWT_EXPIRES_IN'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
imports: [ConfigModule.forRoot({})],
|
||||||
|
inject: [ConfigService],
|
||||||
|
}), ConfigModule.forRoot({})],
|
||||||
|
controllers: [GoogleAuthController, AuthController],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
JwtAuthStrategy,
|
||||||
|
GoogleStrategy,
|
||||||
|
UserRepository,
|
||||||
|
WorkspaceRepository,
|
||||||
|
RefreshTokenRepository,
|
||||||
|
PrismaService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
27
server/src/auth/google.auth.controller.ts
Normal file
27
server/src/auth/google.auth.controller.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { AuthService } from './services/auth.service';
|
||||||
|
import { Profile } from 'passport-google-oauth20';
|
||||||
|
|
||||||
|
@Controller('auth/google')
|
||||||
|
export class GoogleAuthController {
|
||||||
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@UseGuards(AuthGuard('google'))
|
||||||
|
async googleAuth(@Req() req) {}
|
||||||
|
|
||||||
|
@Get('redirect')
|
||||||
|
@UseGuards(AuthGuard('google'))
|
||||||
|
async googleAuthRedirect(@Req() req: Request, @Res() res: Response) {
|
||||||
|
const user = await this.authService.upsertUser(req.user as { firstName: string, lastName: string, email: string })
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).send('User not created');
|
||||||
|
}
|
||||||
|
const refreshToken = await this.authService.registerRefreshToken(user)
|
||||||
|
return res.redirect(this.authService.computeRedirectURI(refreshToken.refreshToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
server/src/auth/guards/jwt.auth.guard.ts
Normal file
5
server/src/auth/guards/jwt.auth.guard.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||||
98
server/src/auth/services/auth.service.ts
Normal file
98
server/src/auth/services/auth.service.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { JwtPayload } from '../strategies/jwt.auth.strategy';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Profile } from 'passport-google-oauth20';
|
||||||
|
import { UserRepository } from 'src/entities/user/user.repository';
|
||||||
|
import { WorkspaceRepository } from 'src/entities/workspace/workspace.repository';
|
||||||
|
import { RefreshTokenRepository } from 'src/entities/refresh-token/refresh-token.repository';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { RefreshToken, User } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
private userRepository: UserRepository,
|
||||||
|
private workspaceRepository: WorkspaceRepository,
|
||||||
|
private refreshTokenRepository: RefreshTokenRepository
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async upsertUser(rawUser: { firstName: string, lastName: string, email: string }) {
|
||||||
|
if (!rawUser.email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawUser.firstName || !rawUser.lastName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailDomain = rawUser.email.split('@')[1];
|
||||||
|
|
||||||
|
if (!emailDomain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspace = await this.workspaceRepository.findUnique({
|
||||||
|
where: { domainName: emailDomain },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.userRepository.upsertUser({
|
||||||
|
data: {
|
||||||
|
id: v4(),
|
||||||
|
email: rawUser.email,
|
||||||
|
displayName: rawUser.firstName + ' ' + rawUser.lastName,
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.userRepository.upsertWorkspaceMember({
|
||||||
|
data: {
|
||||||
|
id: v4(),
|
||||||
|
userId: user.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAccessToken(refreshToken: string) {
|
||||||
|
const refreshTokenObject = this.refreshTokenRepository.findFirst({
|
||||||
|
where: { id: refreshToken },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!refreshTokenObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: JwtPayload = { username: 'Charles', sub: 1 };
|
||||||
|
return {
|
||||||
|
accessToken: this.jwtService.sign(payload),
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerRefreshToken(user: User): Promise<RefreshToken> {
|
||||||
|
const refreshToken = await this.refreshTokenRepository.upsertRefreshToken({
|
||||||
|
data: {
|
||||||
|
id: v4(),
|
||||||
|
userId: user.id,
|
||||||
|
refreshToken: v4(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
computeRedirectURI(refreshToken: string): string {
|
||||||
|
return `${this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')}?refreshToken=${refreshToken}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
server/src/auth/strategies/google.auth.strategy.ts
Normal file
30
server/src/auth/strategies/google.auth.strategy.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||||
|
|
||||||
|
constructor(configService: ConfigService) {
|
||||||
|
super({
|
||||||
|
clientID: configService.get<string>('AUTH_GOOGLE_CLIENT_ID'),
|
||||||
|
clientSecret: configService.get<string>('AUTH_GOOGLE_SECRET'),
|
||||||
|
callbackURL: configService.get<string>('AUTH_GOOGLE_CALLBACK_URL'),
|
||||||
|
scope: ['email', 'profile'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate (accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any> {
|
||||||
|
const { name, emails, photos } = profile
|
||||||
|
const user = {
|
||||||
|
email: emails[0].value,
|
||||||
|
firstName: name.givenName,
|
||||||
|
lastName: name.familyName,
|
||||||
|
picture: photos[0].value,
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
done(null, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
server/src/auth/strategies/jwt.auth.strategy.ts
Normal file
30
server/src/auth/strategies/jwt.auth.strategy.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
export type JwtPayload = { sub: number; username: string };
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
|
constructor(configService: ConfigService) {
|
||||||
|
const extractJwtFromCookie = (req) => {
|
||||||
|
let token = null;
|
||||||
|
|
||||||
|
if (req && req.cookies) {
|
||||||
|
token = req.cookies['jwt'];
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
super({
|
||||||
|
jwtFromRequest: extractJwtFromCookie,
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: JwtPayload) {
|
||||||
|
return { id: payload.sub, username: payload.username };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "RefreshToken" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3),
|
||||||
|
"refreshToken" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@ -29,6 +29,7 @@ model User {
|
|||||||
metadata Json?
|
metadata Json?
|
||||||
WorkspaceMember WorkspaceMember?
|
WorkspaceMember WorkspaceMember?
|
||||||
companies Company[]
|
companies Company[]
|
||||||
|
RefreshTokens RefreshToken[]
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
@ -95,3 +96,15 @@ model Person {
|
|||||||
|
|
||||||
@@map("people")
|
@@map("people")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model RefreshToken {
|
||||||
|
id String @id
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime?
|
||||||
|
refreshToken String
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
|
@@map("refresh_tokens")
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma, RefreshToken } from '@prisma/client';
|
||||||
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RefreshTokenRepository {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async upsertRefreshToken(params: { data: Prisma.RefreshTokenUncheckedCreateInput}): Promise<RefreshToken> {
|
||||||
|
const { data } = params;
|
||||||
|
|
||||||
|
return await this.prisma.refreshToken.upsert({
|
||||||
|
where: {
|
||||||
|
id: data.id,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: data.id,
|
||||||
|
userId: data.userId,
|
||||||
|
refreshToken: data.refreshToken,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findFirst(
|
||||||
|
data: Prisma.RefreshTokenFindFirstArgs,
|
||||||
|
): Promise<RefreshToken | null> {
|
||||||
|
return await this.prisma.refreshToken.findFirst(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { User, Prisma } from '@prisma/client';
|
import { User, Prisma, WorkspaceMember } from '@prisma/client';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -16,4 +16,39 @@ export class UserRepository {
|
|||||||
const { skip, take, cursor, where, orderBy } = params;
|
const { skip, take, cursor, where, orderBy } = params;
|
||||||
return this.prisma.user.findMany({ skip, take, cursor, where, orderBy });
|
return this.prisma.user.findMany({ skip, take, cursor, where, orderBy });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async upsertUser(params: { data: Prisma.UserCreateInput, workspaceId: string }): Promise<User> {
|
||||||
|
const { data } = params;
|
||||||
|
|
||||||
|
return await this.prisma.user.upsert({
|
||||||
|
where: {
|
||||||
|
email: data.email
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: data.id,
|
||||||
|
displayName: data.displayName,
|
||||||
|
email: data.email,
|
||||||
|
locale: data.locale,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertWorkspaceMember(params: { data: Prisma.WorkspaceMemberUncheckedCreateInput }): Promise<WorkspaceMember> {
|
||||||
|
const { data } = params;
|
||||||
|
|
||||||
|
return await this.prisma.workspaceMember.upsert({
|
||||||
|
where: {
|
||||||
|
userId: data.userId
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: data.id,
|
||||||
|
userId: data.userId,
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,4 +16,10 @@ export class WorkspaceRepository {
|
|||||||
const { skip, take, cursor, where, orderBy } = params;
|
const { skip, take, cursor, where, orderBy } = params;
|
||||||
return this.prisma.workspace.findMany({ skip, take, cursor, where, orderBy });
|
return this.prisma.workspace.findMany({ skip, take, cursor, where, orderBy });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findUnique(
|
||||||
|
data: Prisma.WorkspaceFindUniqueArgs,
|
||||||
|
): Promise<Workspace | null> {
|
||||||
|
return await this.prisma.workspace.findUnique(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user