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