mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-26 13:31:45 +03:00
Update token verification and fix typo (#2889)
* Update token verification and fix typo * Fix typo
This commit is contained in:
parent
a48c9293f6
commit
9b7d7b29ed
@ -3,16 +3,17 @@ import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_FILTER, ContextIdFactory, ModuleRef } from '@nestjs/core';
|
||||
|
||||
import { GraphQLError, GraphQLSchema } from 'graphql';
|
||||
import { YogaDriver, YogaDriverConfig } from '@graphql-yoga/nestjs';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { GraphQLError, GraphQLSchema } from 'graphql';
|
||||
import { ExtractJwt } from 'passport-jwt';
|
||||
import { TokenExpiredError, JsonWebTokenError, verify } from 'jsonwebtoken';
|
||||
import { TokenExpiredError, JsonWebTokenError } from 'jsonwebtoken';
|
||||
|
||||
import { WorkspaceFactory } from 'src/workspace/workspace.factory';
|
||||
import { TypeOrmExceptionFilter } from 'src/filters/typeorm-exception.filter';
|
||||
import { HttpExceptionFilter } from 'src/filters/http-exception.filter';
|
||||
import { GlobalExceptionFilter } from 'src/filters/global-exception.filter';
|
||||
import { TokenService } from 'src/core/auth/services/token.service';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@ -20,11 +21,6 @@ import { CoreModule } from './core/core.module';
|
||||
import { IntegrationsModule } from './integrations/integrations.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { WorkspaceModule } from './workspace/workspace.module';
|
||||
import { EnvironmentService } from './integrations/environment/environment.service';
|
||||
import {
|
||||
JwtAuthStrategy,
|
||||
JwtPayload,
|
||||
} from './core/auth/strategies/jwt.auth.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -38,38 +34,19 @@ import {
|
||||
include: [CoreModule],
|
||||
conditionalSchema: async (request) => {
|
||||
try {
|
||||
// Get the JwtAuthStrategy from the AppModule
|
||||
const jwtStrategy = AppModule.moduleRef.get(JwtAuthStrategy, {
|
||||
// Get TokenService from AppModule
|
||||
const tokenService = AppModule.moduleRef.get(TokenService, {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
// Get the EnvironmentService from the AppModule
|
||||
const environmentService = AppModule.moduleRef.get(
|
||||
EnvironmentService,
|
||||
{
|
||||
strict: false,
|
||||
},
|
||||
);
|
||||
let workspace: Workspace;
|
||||
|
||||
// Extract JWT from the request
|
||||
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request.req);
|
||||
|
||||
// If there is no token return an empty schema
|
||||
if (!token) {
|
||||
try {
|
||||
workspace = await tokenService.validateToken(request.req);
|
||||
} catch (err) {
|
||||
return new GraphQLSchema({});
|
||||
}
|
||||
|
||||
// Verify and decode JWT
|
||||
const decoded = verify(
|
||||
token,
|
||||
environmentService.getAccessTokenSecret(),
|
||||
);
|
||||
|
||||
// Validate JWT
|
||||
const { workspace } = await jwtStrategy.validate(
|
||||
decoded as JwtPayload,
|
||||
);
|
||||
|
||||
const contextId = ContextIdFactory.create();
|
||||
|
||||
AppModule.moduleRef.registerRequestByContextId(request, contextId);
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
@ -44,18 +40,10 @@ export class ApiRestQueryBuilderFactory {
|
||||
objectMetadataItems: ObjectMetadataEntity[];
|
||||
objectMetadataItem: ObjectMetadataEntity;
|
||||
}> {
|
||||
let workspaceId;
|
||||
|
||||
try {
|
||||
workspaceId = await this.tokenService.verifyApiKeyToken(request);
|
||||
} catch (err) {
|
||||
throw new UnauthorizedException(
|
||||
`Invalid API key. Double check your API key or generate a new one here ${this.environmentService.getFrontBaseUrl()}/settings/developers/api-keys`,
|
||||
);
|
||||
}
|
||||
const workspace = await this.tokenService.validateToken(request);
|
||||
|
||||
const objectMetadataItems =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspace.id);
|
||||
|
||||
if (!objectMetadataItems.length) {
|
||||
throw new BadRequestException(
|
||||
|
@ -6,13 +6,13 @@ import { mapFieldMetadataToGraphqlQuery } from 'src/core/api-rest/api-rest-query
|
||||
@Injectable()
|
||||
export class CreateQueryFactory {
|
||||
create(objectMetadata, depth?: number): string {
|
||||
const objectNameSingular = capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
return `
|
||||
mutation Create${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}($data: CompanyCreateInput!) {
|
||||
create${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}(data: $data) {
|
||||
mutation Create${objectNameSingular}($data: ${objectNameSingular}CreateInput!) {
|
||||
create${objectNameSingular}(data: $data) {
|
||||
id
|
||||
${objectMetadata.objectMetadataItem.fields
|
||||
.map((field) =>
|
||||
|
@ -5,9 +5,11 @@ import { capitalize } from 'src/utils/capitalize';
|
||||
@Injectable()
|
||||
export class DeleteQueryFactory {
|
||||
create(objectMetadataItem): string {
|
||||
const objectNameSingular = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
return `
|
||||
mutation Delete${capitalize(objectMetadataItem.nameSingular)}($id: ID!) {
|
||||
delete${capitalize(objectMetadataItem.nameSingular)}(id: $id) {
|
||||
mutation Delete${objectNameSingular}($id: ID!) {
|
||||
delete${objectNameSingular}(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,19 @@ import { mapFieldMetadataToGraphqlQuery } from 'src/core/api-rest/api-rest-query
|
||||
@Injectable()
|
||||
export class FindManyQueryFactory {
|
||||
create(objectMetadata, depth?: number): string {
|
||||
const objectNameSingular = capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
);
|
||||
const objectNamePlural = objectMetadata.objectMetadataItem.namePlural;
|
||||
|
||||
return `
|
||||
query FindMany${capitalize(objectMetadata.objectMetadataItem.namePlural)}(
|
||||
$filter: ${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}FilterInput,
|
||||
$orderBy: ${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}OrderByInput,
|
||||
query FindMany${capitalize(objectNamePlural)}(
|
||||
$filter: ${objectNameSingular}FilterInput,
|
||||
$orderBy: ${objectNameSingular}OrderByInput,
|
||||
$lastCursor: String,
|
||||
$limit: Float = 60
|
||||
) {
|
||||
${objectMetadata.objectMetadataItem.namePlural}(
|
||||
${objectNamePlural}(
|
||||
filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor
|
||||
) {
|
||||
edges {
|
||||
|
@ -6,15 +6,13 @@ import { mapFieldMetadataToGraphqlQuery } from 'src/core/api-rest/api-rest-query
|
||||
@Injectable()
|
||||
export class FindOneQueryFactory {
|
||||
create(objectMetadata, depth?: number): string {
|
||||
const objectNameSingular = objectMetadata.objectMetadataItem.nameSingular;
|
||||
|
||||
return `
|
||||
query FindOne${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}(
|
||||
$filter: ${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}FilterInput!,
|
||||
query FindOne${capitalize(objectNameSingular)}(
|
||||
$filter: ${capitalize(objectNameSingular)}FilterInput!,
|
||||
) {
|
||||
${objectMetadata.objectMetadataItem.nameSingular}(filter: $filter) {
|
||||
${objectNameSingular}(filter: $filter) {
|
||||
id
|
||||
${objectMetadata.objectMetadataItem.fields
|
||||
.map((field) =>
|
||||
|
@ -6,13 +6,13 @@ import { mapFieldMetadataToGraphqlQuery } from 'src/core/api-rest/api-rest-query
|
||||
@Injectable()
|
||||
export class UpdateQueryFactory {
|
||||
create(objectMetadata, depth?: number): string {
|
||||
const objectNameSingular = objectMetadata.objectMetadataItem.nameSingular;
|
||||
|
||||
return `
|
||||
mutation Update${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}($id: ID!, $data: CompanyUpdateInput!) {
|
||||
update${capitalize(
|
||||
objectMetadata.objectMetadataItem.nameSingular,
|
||||
)}(id: $id, data: $data) {
|
||||
objectNameSingular,
|
||||
)}($id: ID!, $data: ${capitalize(objectNameSingular)}UpdateInput!) {
|
||||
update${capitalize(objectNameSingular)}(id: $id, data: $data) {
|
||||
id
|
||||
${objectMetadata.objectMetadataItem.fields
|
||||
.map((field) =>
|
||||
|
@ -5,6 +5,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { JwtAuthStrategy } from 'src/core/auth/strategies/jwt.auth.strategy';
|
||||
|
||||
import { TokenService } from './token.service';
|
||||
|
||||
@ -19,6 +20,10 @@ describe('TokenService', () => {
|
||||
provide: JwtService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: JwtAuthStrategy,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
|
@ -11,22 +11,27 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { TokenExpiredError } from 'jsonwebtoken';
|
||||
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Request } from 'express';
|
||||
import { ExtractJwt } from 'passport-jwt';
|
||||
|
||||
import { JwtPayload } from 'src/core/auth/strategies/jwt.auth.strategy';
|
||||
import {
|
||||
JwtAuthStrategy,
|
||||
JwtPayload,
|
||||
} from 'src/core/auth/strategies/jwt.auth.strategy';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { ApiKeyToken, AuthToken } from 'src/core/auth/dto/token.entity';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TokenService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly jwtStrategy: JwtAuthStrategy,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@ -167,18 +172,22 @@ export class TokenService {
|
||||
return { token };
|
||||
}
|
||||
|
||||
async verifyApiKeyToken(request: Request) {
|
||||
async validateToken(request: Request): Promise<Workspace> {
|
||||
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
||||
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('missing authentication token');
|
||||
}
|
||||
const payload = await this.verifyJwt(
|
||||
const decoded = await this.verifyJwt(
|
||||
token,
|
||||
this.environmentService.getAccessTokenSecret(),
|
||||
);
|
||||
|
||||
return payload.workspaceId;
|
||||
const { workspace } = await this.jwtStrategy.validate(
|
||||
decoded as JwtPayload,
|
||||
);
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
async verifyLoginToken(loginToken: string): Promise<string> {
|
||||
@ -290,6 +299,8 @@ export class TokenService {
|
||||
} catch (error) {
|
||||
if (error instanceof TokenExpiredError) {
|
||||
throw new UnauthorizedException('Token has expired.');
|
||||
} else if (error instanceof JsonWebTokenError) {
|
||||
throw new UnauthorizedException('Token invalid.');
|
||||
} else {
|
||||
throw new UnprocessableEntityException();
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
);
|
||||
|
||||
assert(
|
||||
apiKey.length === 1 && !apiKey[0].revokedAt,
|
||||
apiKey.length === 1 && !apiKey?.[0].revokedAt,
|
||||
'This API Key is revoked',
|
||||
ForbiddenException,
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user