1895 extensibility v1 application tokens 3 (#16504)
- moves applicationRoleId to application entity - add new `APPLICATION` FieldActorSource and `APPLICATION` JwtTokenTypeEnum value - create a new token with applicationId when executing a function - when applicationId is in token, check for application.defaultRole permissions -use twenty-shared types in `twenty-sdk/application` - create a new import from generate called "Twenty" that you can use directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep metadata or core parameter only) - provide to serverless unique one time BEARER TOKEN to run it Result <img width="977" height="566" alt="image" src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c" /> <img width="910" height="596" alt="image" src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324" /> <img width="741" height="568" alt="image" src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f" />
This commit is contained in:
@@ -13,6 +13,7 @@ export const AuthExceptionCode = appendCommonExceptionCode({
|
||||
EMAIL_NOT_VERIFIED: 'EMAIL_NOT_VERIFIED',
|
||||
CLIENT_NOT_FOUND: 'CLIENT_NOT_FOUND',
|
||||
WORKSPACE_NOT_FOUND: 'WORKSPACE_NOT_FOUND',
|
||||
APPLICATION_NOT_FOUND: 'APPLICATION_NOT_FOUND',
|
||||
INVALID_INPUT: 'INVALID_INPUT',
|
||||
FORBIDDEN_EXCEPTION: 'FORBIDDEN_EXCEPTION',
|
||||
INSUFFICIENT_SCOPES: 'INSUFFICIENT_SCOPES',
|
||||
|
||||
@@ -63,6 +63,7 @@ import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/se
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
import { TwoFactorAuthenticationMethodEntity } from '../two-factor-authentication/entities/two-factor-authentication-method.entity';
|
||||
import { TwoFactorAuthenticationModule } from '../two-factor-authentication/two-factor-authentication.module';
|
||||
@@ -87,6 +88,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
UserEntity,
|
||||
AppTokenEntity,
|
||||
ApiKeyEntity,
|
||||
ApplicationEntity,
|
||||
FeatureFlagEntity,
|
||||
WorkspaceSSOIdentityProviderEntity,
|
||||
KeyValuePairEntity,
|
||||
|
||||
+81
-18
@@ -4,7 +4,10 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type JwtPayload } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import {
|
||||
type JwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
import { JwtAuthStrategy } from './jwt.auth.strategy';
|
||||
@@ -24,6 +27,7 @@ describe('JwtAuthStrategy', () => {
|
||||
let userWorkspaceRepository: any;
|
||||
let userRepository: any;
|
||||
let apiKeyRepository: any;
|
||||
let applicationRepository: any;
|
||||
let jwtWrapperService: any;
|
||||
let permissionsService: any;
|
||||
|
||||
@@ -49,6 +53,10 @@ describe('JwtAuthStrategy', () => {
|
||||
findOne: jest.fn(),
|
||||
};
|
||||
|
||||
applicationRepository = {
|
||||
findOne: jest.fn(),
|
||||
};
|
||||
|
||||
jwtWrapperService = {
|
||||
extractJwtFromRequest: jest.fn(() => () => 'token'),
|
||||
};
|
||||
@@ -66,7 +74,7 @@ describe('JwtAuthStrategy', () => {
|
||||
it('should throw AuthException if type is API_KEY and workspace is not found', async () => {
|
||||
const payload = {
|
||||
...jwt,
|
||||
type: 'API_KEY',
|
||||
type: JwtTokenTypeEnum.API_KEY,
|
||||
};
|
||||
|
||||
workspaceRepository.findOneBy.mockResolvedValue(null);
|
||||
@@ -74,6 +82,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -91,7 +100,7 @@ describe('JwtAuthStrategy', () => {
|
||||
it('should throw AuthExceptionCode if type is API_KEY not found', async () => {
|
||||
const payload = {
|
||||
...jwt,
|
||||
type: 'API_KEY',
|
||||
type: JwtTokenTypeEnum.API_KEY,
|
||||
};
|
||||
|
||||
const mockWorkspace = new WorkspaceEntity();
|
||||
@@ -104,6 +113,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -121,7 +131,7 @@ describe('JwtAuthStrategy', () => {
|
||||
it('should throw AuthExceptionCode if API_KEY is revoked', async () => {
|
||||
const payload = {
|
||||
...jwt,
|
||||
type: 'API_KEY',
|
||||
type: JwtTokenTypeEnum.API_KEY,
|
||||
};
|
||||
|
||||
const mockWorkspace = new WorkspaceEntity();
|
||||
@@ -137,6 +147,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -154,7 +165,7 @@ describe('JwtAuthStrategy', () => {
|
||||
it('should be truthy if type is API_KEY and API_KEY is not revoked', async () => {
|
||||
const payload = {
|
||||
...jwt,
|
||||
type: 'API_KEY',
|
||||
type: JwtTokenTypeEnum.API_KEY,
|
||||
};
|
||||
|
||||
const mockWorkspace = new WorkspaceEntity();
|
||||
@@ -170,6 +181,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -198,7 +210,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
};
|
||||
@@ -210,6 +222,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -234,7 +247,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
};
|
||||
@@ -248,6 +261,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -272,7 +286,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
};
|
||||
@@ -290,6 +304,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -303,6 +318,44 @@ describe('JwtAuthStrategy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('APPLICATION token validation', () => {
|
||||
it('should throw AuthExceptionCode if type is APPLICATION, and application not found', async () => {
|
||||
const validApplicationId = randomUUID();
|
||||
const validWorkspaceId = randomUUID();
|
||||
|
||||
const payload = {
|
||||
sub: validApplicationId,
|
||||
type: JwtTokenTypeEnum.APPLICATION,
|
||||
applicationId: validApplicationId,
|
||||
workspaceId: validWorkspaceId,
|
||||
};
|
||||
|
||||
workspaceRepository.findOneBy.mockResolvedValue(new WorkspaceEntity());
|
||||
|
||||
applicationRepository.findOne.mockResolvedValue(null);
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
permissionsService,
|
||||
);
|
||||
|
||||
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
|
||||
new AuthException('Application not found', expect.any(String)),
|
||||
);
|
||||
|
||||
try {
|
||||
await strategy.validate(payload as JwtPayload);
|
||||
} catch (e) {
|
||||
expect(e.code).toBe(AuthExceptionCode.APPLICATION_NOT_FOUND);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Impersonation validation', () => {
|
||||
it('should throw AuthException if impersonation token has missing impersonatorUserWorkspaceId', async () => {
|
||||
const validUserId = randomUUID();
|
||||
@@ -311,7 +364,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -335,6 +388,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -357,7 +411,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -380,6 +434,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -401,7 +456,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -427,6 +482,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -449,7 +505,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -485,6 +541,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -507,7 +564,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -537,6 +594,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -560,7 +618,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -607,6 +665,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -629,7 +688,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -676,6 +735,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -699,7 +759,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -746,6 +806,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -768,7 +829,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -809,6 +870,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
@@ -837,7 +899,7 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
@@ -874,6 +936,7 @@ describe('JwtAuthStrategy', () => {
|
||||
strategy = new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
workspaceRepository,
|
||||
applicationRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
apiKeyRepository,
|
||||
|
||||
+51
-6
@@ -16,9 +16,11 @@ import {
|
||||
import {
|
||||
type AccessTokenJwtPayload,
|
||||
type ApiKeyTokenJwtPayload,
|
||||
ApplicationTokenJwtPayload,
|
||||
type AuthContext,
|
||||
type FileTokenJwtPayload,
|
||||
type JwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
type WorkspaceAgnosticTokenJwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
@@ -27,12 +29,16 @@ import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly jwtWrapperService: JwtWrapperService,
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly userRepository: Repository<UserEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
@@ -52,7 +58,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
>(rawJwtToken);
|
||||
|
||||
const appSecretBody =
|
||||
decodedToken.type === 'WORKSPACE_AGNOSTIC'
|
||||
decodedToken.type === JwtTokenTypeEnum.WORKSPACE_AGNOSTIC
|
||||
? decodedToken.userId
|
||||
: decodedToken.workspaceId;
|
||||
|
||||
@@ -270,7 +276,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
|
||||
private async validateWorkspaceAgnosticToken(
|
||||
payload: WorkspaceAgnosticTokenJwtPayload,
|
||||
) {
|
||||
): Promise<AuthContext> {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: payload.sub },
|
||||
});
|
||||
@@ -283,6 +289,39 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
return { user, authProvider: payload.authProvider };
|
||||
}
|
||||
|
||||
private async validateApplicationToken(
|
||||
payload: ApplicationTokenJwtPayload,
|
||||
): Promise<AuthContext> {
|
||||
const workspace = await this.workspaceRepository.findOneBy({
|
||||
id: payload.workspaceId,
|
||||
});
|
||||
|
||||
if (!isDefined(workspace)) {
|
||||
throw new AuthException(
|
||||
'Workspace not found',
|
||||
AuthExceptionCode.WORKSPACE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const applicationId = payload.sub ?? payload.applicationId;
|
||||
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId },
|
||||
});
|
||||
|
||||
if (!isDefined(application)) {
|
||||
throw new AuthException(
|
||||
'Application not found',
|
||||
AuthExceptionCode.APPLICATION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
application,
|
||||
workspace,
|
||||
};
|
||||
}
|
||||
|
||||
private isLegacyApiKeyPayload(
|
||||
payload: JwtPayload,
|
||||
): payload is ApiKeyTokenJwtPayload {
|
||||
@@ -291,19 +330,25 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
|
||||
async validate(payload: JwtPayload): Promise<AuthContext> {
|
||||
// Support legacy api keys
|
||||
if (payload.type === 'API_KEY' || this.isLegacyApiKeyPayload(payload)) {
|
||||
if (
|
||||
payload.type === JwtTokenTypeEnum.API_KEY ||
|
||||
this.isLegacyApiKeyPayload(payload)
|
||||
) {
|
||||
return await this.validateAPIKey(payload);
|
||||
}
|
||||
|
||||
if (payload.type === 'WORKSPACE_AGNOSTIC') {
|
||||
if (payload.type === JwtTokenTypeEnum.WORKSPACE_AGNOSTIC) {
|
||||
return await this.validateWorkspaceAgnosticToken(payload);
|
||||
}
|
||||
|
||||
// `!payload.type` is here to support legacy token
|
||||
if (payload.type === 'ACCESS' || !payload.type) {
|
||||
if (payload.type === JwtTokenTypeEnum.ACCESS) {
|
||||
return await this.validateAccessToken(payload);
|
||||
}
|
||||
|
||||
if (payload.type === JwtTokenTypeEnum.APPLICATION) {
|
||||
return await this.validateApplicationToken(payload);
|
||||
}
|
||||
|
||||
throw new AuthException(
|
||||
'Invalid token',
|
||||
AuthExceptionCode.INVALID_JWT_TOKEN_TYPE,
|
||||
|
||||
+1
-4
@@ -265,10 +265,7 @@ describe('AccessTokenService', () => {
|
||||
const result = await service.validateTokenByRequest(mockRequest);
|
||||
|
||||
expect(result).toEqual(mockAuthContext);
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(
|
||||
mockToken,
|
||||
'ACCESS',
|
||||
);
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(mockToken);
|
||||
expect(jwtWrapperService.decode).toHaveBeenCalledWith(mockToken);
|
||||
expect(service['jwtStrategy'].validate).toHaveBeenCalledWith(
|
||||
mockDecodedToken,
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ export class AccessTokenService {
|
||||
}
|
||||
|
||||
async validateToken(token: string): Promise<AuthContext> {
|
||||
await this.jwtWrapperService.verifyJwtToken(token, JwtTokenTypeEnum.ACCESS);
|
||||
await this.jwtWrapperService.verifyJwtToken(token);
|
||||
|
||||
const decoded = this.jwtWrapperService.decode<AccessTokenJwtPayload>(token);
|
||||
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationException } from 'src/engine/core-modules/application/application.exception';
|
||||
import { WorkspaceException } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
describe('ApplicationTokenService', () => {
|
||||
let service: ApplicationTokenService;
|
||||
let jwtWrapperService: JwtWrapperService;
|
||||
let workspaceRepository: Repository<WorkspaceEntity>;
|
||||
let applicationRepository: Repository<ApplicationEntity>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ApplicationTokenService,
|
||||
{
|
||||
provide: JwtWrapperService,
|
||||
useValue: {
|
||||
sign: jest.fn(),
|
||||
verifyJwtToken: jest.fn(),
|
||||
decode: jest.fn(),
|
||||
generateAppSecret: jest.fn(),
|
||||
extractJwtFromRequest: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(ApplicationEntity),
|
||||
useClass: Repository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(WorkspaceEntity),
|
||||
useClass: Repository,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ApplicationTokenService>(ApplicationTokenService);
|
||||
jwtWrapperService = module.get<JwtWrapperService>(JwtWrapperService);
|
||||
applicationRepository = module.get<Repository<ApplicationEntity>>(
|
||||
getRepositoryToken(ApplicationEntity),
|
||||
);
|
||||
workspaceRepository = module.get<Repository<WorkspaceEntity>>(
|
||||
getRepositoryToken(WorkspaceEntity),
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('generateApplicationToken', () => {
|
||||
it('should generate an application token successfully', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const applicationId = 'application-id';
|
||||
const mockWorkspace = { id: workspaceId };
|
||||
const mockApplication = { id: applicationId };
|
||||
const mockToken = 'mock-token';
|
||||
|
||||
jest
|
||||
.spyOn(workspaceRepository, 'findOne')
|
||||
.mockResolvedValue(mockWorkspace as WorkspaceEntity);
|
||||
jest
|
||||
.spyOn(applicationRepository, 'findOne')
|
||||
.mockResolvedValue(mockApplication as ApplicationEntity);
|
||||
jest.spyOn(jwtWrapperService, 'sign').mockReturnValue(mockToken);
|
||||
|
||||
const result = await service.generateApplicationToken({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
expiresInSeconds: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
token: mockToken,
|
||||
expiresAt: expect.any(Date),
|
||||
});
|
||||
expect(jwtWrapperService.sign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sub: applicationId,
|
||||
applicationId,
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing userId successfully', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const applicationId = 'application-id';
|
||||
const mockWorkspace = { id: workspaceId };
|
||||
const mockApplication = { id: applicationId };
|
||||
const mockToken = 'mock-token';
|
||||
|
||||
jest
|
||||
.spyOn(workspaceRepository, 'findOne')
|
||||
.mockResolvedValue(mockWorkspace as WorkspaceEntity);
|
||||
jest
|
||||
.spyOn(applicationRepository, 'findOne')
|
||||
.mockResolvedValue(mockApplication as ApplicationEntity);
|
||||
jest.spyOn(jwtWrapperService, 'sign').mockReturnValue(mockToken);
|
||||
|
||||
const result = await service.generateApplicationToken({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
expiresInSeconds: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
token: mockToken,
|
||||
expiresAt: expect.any(Date),
|
||||
});
|
||||
expect(jwtWrapperService.sign).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sub: applicationId,
|
||||
applicationId,
|
||||
workspaceId: workspaceId,
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if application is not found', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
const mockWorkspace = { id: workspaceId };
|
||||
|
||||
jest.spyOn(applicationRepository, 'findOne').mockResolvedValue(null);
|
||||
jest
|
||||
.spyOn(workspaceRepository, 'findOne')
|
||||
.mockResolvedValue(mockWorkspace as WorkspaceEntity);
|
||||
|
||||
await expect(
|
||||
service.generateApplicationToken({
|
||||
applicationId: 'non-existent-application',
|
||||
workspaceId: 'workspace-id',
|
||||
expiresInSeconds: 10,
|
||||
}),
|
||||
).rejects.toThrow(ApplicationException);
|
||||
});
|
||||
|
||||
it('should throw an error if workspace is not found', async () => {
|
||||
jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.generateApplicationToken({
|
||||
applicationId: 'application-id',
|
||||
workspaceId: 'non-existent-workspace',
|
||||
expiresInSeconds: 10,
|
||||
}),
|
||||
).rejects.toThrow(WorkspaceException);
|
||||
});
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import ms from 'ms';
|
||||
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import {
|
||||
ApplicationTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationTokenService {
|
||||
constructor(
|
||||
private readonly jwtWrapperService: JwtWrapperService,
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {}
|
||||
|
||||
async generateApplicationToken({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
expiresInSeconds,
|
||||
}: Omit<ApplicationTokenJwtPayload, 'type' | 'sub'> & {
|
||||
expiresInSeconds: number;
|
||||
}): Promise<AuthToken> {
|
||||
const expiresIn = `${expiresInSeconds}s`;
|
||||
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: workspaceId },
|
||||
});
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId, workspaceId },
|
||||
});
|
||||
|
||||
assertIsDefinedOrThrow(
|
||||
application,
|
||||
new ApplicationException(
|
||||
'Application not found',
|
||||
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
const jwtPayload: ApplicationTokenJwtPayload = {
|
||||
sub: applicationId,
|
||||
applicationId,
|
||||
workspaceId,
|
||||
type: JwtTokenTypeEnum.APPLICATION,
|
||||
};
|
||||
|
||||
return {
|
||||
token: this.jwtWrapperService.sign(jwtPayload, {
|
||||
secret: this.jwtWrapperService.generateAppSecret(
|
||||
JwtTokenTypeEnum.APPLICATION,
|
||||
workspaceId,
|
||||
),
|
||||
expiresIn,
|
||||
}),
|
||||
expiresAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
+6
-8
@@ -3,6 +3,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
|
||||
import { LoginTokenService } from './login-token.service';
|
||||
|
||||
@@ -67,7 +68,7 @@ describe('LoginTokenService', () => {
|
||||
expiresAt: expect.any(Date),
|
||||
});
|
||||
expect(jwtWrapperService.generateAppSecret).toHaveBeenCalledWith(
|
||||
'LOGIN',
|
||||
JwtTokenTypeEnum.LOGIN,
|
||||
workspaceId,
|
||||
);
|
||||
expect(twentyConfigService.get).toHaveBeenCalledWith(
|
||||
@@ -77,7 +78,7 @@ describe('LoginTokenService', () => {
|
||||
{
|
||||
sub: email,
|
||||
workspaceId,
|
||||
type: 'LOGIN',
|
||||
type: JwtTokenTypeEnum.LOGIN,
|
||||
authProvider: AuthProviderEnum.Password,
|
||||
impersonatorUserId: undefined,
|
||||
},
|
||||
@@ -112,14 +113,14 @@ describe('LoginTokenService', () => {
|
||||
expiresAt: expect.any(Date),
|
||||
});
|
||||
expect(jwtWrapperService.generateAppSecret).toHaveBeenCalledWith(
|
||||
'LOGIN',
|
||||
JwtTokenTypeEnum.LOGIN,
|
||||
workspaceId,
|
||||
);
|
||||
expect(jwtWrapperService.sign).toHaveBeenCalledWith(
|
||||
{
|
||||
sub: email,
|
||||
workspaceId,
|
||||
type: 'LOGIN',
|
||||
type: JwtTokenTypeEnum.LOGIN,
|
||||
authProvider: AuthProviderEnum.Impersonation,
|
||||
impersonatorUserWorkspaceId,
|
||||
},
|
||||
@@ -143,10 +144,7 @@ describe('LoginTokenService', () => {
|
||||
const result = await service.verifyLoginToken(mockToken);
|
||||
|
||||
expect(result).toEqual({ sub: mockEmail });
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(
|
||||
mockToken,
|
||||
'LOGIN',
|
||||
);
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(mockToken);
|
||||
expect(jwtWrapperService.decode).toHaveBeenCalledWith(mockToken, {
|
||||
json: true,
|
||||
});
|
||||
|
||||
+1
-4
@@ -52,10 +52,7 @@ export class LoginTokenService {
|
||||
}
|
||||
|
||||
async verifyLoginToken(loginToken: string): Promise<LoginTokenJwtPayload> {
|
||||
await this.jwtWrapperService.verifyJwtToken(
|
||||
loginToken,
|
||||
JwtTokenTypeEnum.LOGIN,
|
||||
);
|
||||
await this.jwtWrapperService.verifyJwtToken(loginToken);
|
||||
|
||||
return this.jwtWrapperService.decode(loginToken, {
|
||||
json: true,
|
||||
|
||||
+5
-8
@@ -100,10 +100,7 @@ describe('RefreshTokenService', () => {
|
||||
const result = await service.verifyRefreshToken(mockToken);
|
||||
|
||||
expect(result).toEqual({ user: mockUser, token: mockAppToken });
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(
|
||||
mockToken,
|
||||
'REFRESH',
|
||||
);
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(mockToken);
|
||||
});
|
||||
|
||||
it('should throw an error if the token is malformed', async () => {
|
||||
@@ -154,9 +151,9 @@ describe('RefreshTokenService', () => {
|
||||
{
|
||||
sub: userId,
|
||||
workspaceId,
|
||||
type: 'REFRESH',
|
||||
type: JwtTokenTypeEnum.REFRESH,
|
||||
userId: 'user-id',
|
||||
targetedTokenType: 'ACCESS',
|
||||
targetedTokenType: JwtTokenTypeEnum.ACCESS,
|
||||
},
|
||||
expect.objectContaining({
|
||||
secret: 'mock-secret',
|
||||
@@ -190,8 +187,8 @@ describe('RefreshTokenService', () => {
|
||||
(jwtWrapperService.decode as jest.Mock).mockReturnValue({
|
||||
sub: userId,
|
||||
jti: tokenId,
|
||||
type: 'REFRESH',
|
||||
targetedTokenType: 'ACCESS',
|
||||
type: JwtTokenTypeEnum.REFRESH,
|
||||
targetedTokenType: JwtTokenTypeEnum.ACCESS,
|
||||
isImpersonating: true,
|
||||
impersonatorUserWorkspaceId: 'uw-imp',
|
||||
impersonatedUserWorkspaceId: 'uw-orig',
|
||||
|
||||
+1
-4
@@ -36,10 +36,7 @@ export class RefreshTokenService {
|
||||
async verifyRefreshToken(refreshToken: string) {
|
||||
const coolDown = this.twentyConfigService.get('REFRESH_TOKEN_COOL_DOWN');
|
||||
|
||||
await this.jwtWrapperService.verifyJwtToken(
|
||||
refreshToken,
|
||||
JwtTokenTypeEnum.REFRESH,
|
||||
);
|
||||
await this.jwtWrapperService.verifyJwtToken(refreshToken);
|
||||
const jwtPayload =
|
||||
this.jwtWrapperService.decode<RefreshTokenJwtPayload>(refreshToken);
|
||||
|
||||
|
||||
+3
-5
@@ -2,6 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
|
||||
import { TransientTokenService } from './transient-token.service';
|
||||
|
||||
@@ -72,7 +73,7 @@ describe('TransientTokenService', () => {
|
||||
expect(jwtWrapperService.sign).toHaveBeenCalledWith(
|
||||
{
|
||||
sub: workspaceMemberId,
|
||||
type: 'LOGIN',
|
||||
type: JwtTokenTypeEnum.LOGIN,
|
||||
userId,
|
||||
workspaceId,
|
||||
workspaceMemberId,
|
||||
@@ -108,10 +109,7 @@ describe('TransientTokenService', () => {
|
||||
userId: mockPayload.userId,
|
||||
workspaceId: mockPayload.workspaceId,
|
||||
});
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(
|
||||
mockToken,
|
||||
'LOGIN',
|
||||
);
|
||||
expect(jwtWrapperService.verifyJwtToken).toHaveBeenCalledWith(mockToken);
|
||||
expect(jwtWrapperService.decode).toHaveBeenCalledWith(mockToken);
|
||||
});
|
||||
|
||||
|
||||
+1
-4
@@ -53,10 +53,7 @@ export class TransientTokenService {
|
||||
async verifyTransientToken(
|
||||
transientToken: string,
|
||||
): Promise<Omit<TransientTokenJwtPayload, 'type' | 'sub'>> {
|
||||
await this.jwtWrapperService.verifyJwtToken(
|
||||
transientToken,
|
||||
JwtTokenTypeEnum.LOGIN,
|
||||
);
|
||||
await this.jwtWrapperService.verifyJwtToken(transientToken);
|
||||
|
||||
const { type: _type, ...payload } =
|
||||
this.jwtWrapperService.decode<TransientTokenJwtPayload>(transientToken);
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
|
||||
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
|
||||
describe('WorkspaceAgnosticToken', () => {
|
||||
let service: WorkspaceAgnosticTokenService;
|
||||
@@ -95,7 +96,7 @@ describe('WorkspaceAgnosticToken', () => {
|
||||
authProvider: AuthProviderEnum.Password,
|
||||
sub: userId,
|
||||
userId: userId,
|
||||
type: 'WORKSPACE_AGNOSTIC',
|
||||
type: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC,
|
||||
},
|
||||
expect.objectContaining({
|
||||
secret: 'mocked-secret',
|
||||
@@ -132,7 +133,7 @@ describe('WorkspaceAgnosticToken', () => {
|
||||
const mockPayload = {
|
||||
sub: userId,
|
||||
userId: userId,
|
||||
type: 'WORKSPACE_AGNOSTIC',
|
||||
type: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC,
|
||||
};
|
||||
const mockUser = { id: userId };
|
||||
|
||||
@@ -177,7 +178,7 @@ describe('WorkspaceAgnosticToken', () => {
|
||||
const mockPayload = {
|
||||
sub: userId,
|
||||
userId: userId,
|
||||
type: 'WORKSPACE_AGNOSTIC',
|
||||
type: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC,
|
||||
};
|
||||
|
||||
jest.spyOn(jwtWrapperService, 'decode').mockReturnValue(mockPayload);
|
||||
|
||||
@@ -16,6 +16,8 @@ import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -26,6 +28,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
WorkspaceEntity,
|
||||
UserWorkspaceEntity,
|
||||
ApiKeyEntity,
|
||||
ApplicationEntity,
|
||||
]),
|
||||
TypeORMModule,
|
||||
DataSourceModule,
|
||||
@@ -38,6 +41,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
LoginTokenService,
|
||||
RefreshTokenService,
|
||||
WorkspaceAgnosticTokenService,
|
||||
ApplicationTokenService,
|
||||
],
|
||||
exports: [
|
||||
RenewTokenService,
|
||||
@@ -45,6 +49,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
LoginTokenService,
|
||||
RefreshTokenService,
|
||||
WorkspaceAgnosticTokenService,
|
||||
ApplicationTokenService,
|
||||
],
|
||||
})
|
||||
export class TokenModule {}
|
||||
|
||||
@@ -3,12 +3,14 @@ import { type UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace
|
||||
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
export type AuthContext = {
|
||||
user?: UserEntity | null | undefined;
|
||||
apiKey?: ApiKeyEntity | null | undefined;
|
||||
workspaceMemberId?: string;
|
||||
workspace?: WorkspaceEntity;
|
||||
application?: ApplicationEntity | null | undefined;
|
||||
userWorkspaceId?: string;
|
||||
userWorkspace?: UserWorkspaceEntity;
|
||||
authProvider?: AuthProviderEnum;
|
||||
@@ -28,6 +30,7 @@ export enum JwtTokenTypeEnum {
|
||||
POSTGRES_PROXY = 'POSTGRES_PROXY',
|
||||
REMOTE_SERVER = 'REMOTE_SERVER',
|
||||
KEY_ENCRYPTION_KEY = 'KEY_ENCRYPTION_KEY',
|
||||
APPLICATION = 'APPLICATION',
|
||||
}
|
||||
|
||||
type CommonPropertiesJwtPayload = {
|
||||
@@ -83,6 +86,12 @@ export type ApiKeyTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
jti?: string;
|
||||
};
|
||||
|
||||
export type ApplicationTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPLICATION;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
};
|
||||
|
||||
export type AccessTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.ACCESS;
|
||||
workspaceId: string;
|
||||
@@ -106,6 +115,7 @@ export type RemoteServerTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
export type JwtPayload =
|
||||
| AccessTokenJwtPayload
|
||||
| ApiKeyTokenJwtPayload
|
||||
| ApplicationTokenJwtPayload
|
||||
| WorkspaceAgnosticTokenJwtPayload
|
||||
| LoginTokenJwtPayload
|
||||
| TransientTokenJwtPayload
|
||||
|
||||
+1
@@ -52,6 +52,7 @@ export const authGraphqlApiExceptionHandler = (exception: AuthException) => {
|
||||
});
|
||||
case AuthExceptionCode.USER_NOT_FOUND:
|
||||
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
case AuthExceptionCode.APPLICATION_NOT_FOUND:
|
||||
case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
throw new AuthenticationError(exception);
|
||||
case AuthExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ export const getAuthExceptionRestStatus = (exception: AuthException) => {
|
||||
case AuthExceptionCode.UNAUTHENTICATED:
|
||||
case AuthExceptionCode.USER_NOT_FOUND:
|
||||
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
case AuthExceptionCode.APPLICATION_NOT_FOUND:
|
||||
return 401;
|
||||
case AuthExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
|
||||
Reference in New Issue
Block a user