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:
martmull
2025-12-15 17:44:23 +01:00
committed by GitHub
parent e33f18bfa8
commit e289f3056e
103 changed files with 1427 additions and 512 deletions
@@ -17,13 +17,8 @@ import {
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import {
type AccessTokenJwtPayload,
type FileTokenJwtPayload,
type JwtPayload,
JwtTokenTypeEnum,
type RefreshTokenJwtPayload,
type TransientTokenJwtPayload,
type WorkspaceAgnosticTokenJwtPayload,
} from 'src/engine/core-modules/auth/types/auth-context.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -54,16 +49,10 @@ export class JwtWrapperService {
verifyJwtToken(
token: string,
type: JwtTokenTypeEnum,
options?: JwtVerifyOptions,
isLegacyApiKey = false,
) {
const payload = this.decode<
| TransientTokenJwtPayload
| RefreshTokenJwtPayload
| WorkspaceAgnosticTokenJwtPayload
| AccessTokenJwtPayload
| FileTokenJwtPayload
>(token, {
const payload = this.decode<JwtPayload>(token, {
json: true,
});
@@ -71,37 +60,56 @@ export class JwtWrapperService {
throw new AuthException('No payload', AuthExceptionCode.UNAUTHENTICATED);
}
// @TODO: Migrate to use type from payload instead of parameter
type =
payload.type === JwtTokenTypeEnum.WORKSPACE_AGNOSTIC
? JwtTokenTypeEnum.WORKSPACE_AGNOSTIC
: type;
const type = payload.type;
// TODO: check if this is really needed
if (type !== 'FILE' && !payload.sub) {
const appSecretBody =
'workspaceId' in payload
? payload.workspaceId
: 'userId' in payload
? payload.userId
: undefined;
if (!isDefined(appSecretBody)) {
throw new AuthException(
'No payload sub',
AuthExceptionCode.UNAUTHENTICATED,
'Invalid token type',
AuthExceptionCode.INVALID_JWT_TOKEN_TYPE,
);
}
try {
// TODO: Deprecate this once old API KEY tokens are no longer in use
if (!payload.type && !('workspaceId' in payload) && type === 'ACCESS') {
// Supporting old API KEY tokens
if (
!payload.type &&
!('workspaceId' in payload) &&
type === JwtTokenTypeEnum.API_KEY
) {
return this.jwtService.verify(token, {
...options,
secret: this.generateAppSecretLegacy(),
});
}
const appSecretBody =
'workspaceId' in payload ? payload.workspaceId : payload.userId;
if (!isDefined(appSecretBody)) {
throw new AuthException(
'Invalid token type',
AuthExceptionCode.INVALID_JWT_TOKEN_TYPE,
);
// This is due to an unfortunate mistake in the secret generation of API_KEY
// tokens. We used to sign with ACCESS Jwt Token Type instead of API_KEY.
// Now we need to check both cases not to break the existing api keys
// See this PR for context -> https://github.com/twentyhq/twenty/pull/16504
// This code block can be deleted, but all api keys created before
// 12/12/2025 will be broken
if (type === JwtTokenTypeEnum.API_KEY && !isLegacyApiKey) {
try {
return this.jwtService.verify(token, {
...options,
secret: this.generateAppSecret(type, appSecretBody),
});
} catch {
return this.jwtService.verify(token, {
...options,
secret: this.generateAppSecret(
JwtTokenTypeEnum.ACCESS,
appSecretBody,
),
});
}
}
return this.jwtService.verify(token, {
@@ -114,17 +122,17 @@ export class JwtWrapperService {
'Token has expired.',
AuthExceptionCode.UNAUTHENTICATED,
);
} else if (error instanceof jwt.JsonWebTokenError) {
}
if (error instanceof jwt.JsonWebTokenError) {
throw new AuthException(
'Token invalid.',
AuthExceptionCode.UNAUTHENTICATED,
);
} else {
throw new AuthException(
'Unknown token error.',
AuthExceptionCode.INVALID_INPUT,
);
}
throw new AuthException(
'Unknown token error.',
AuthExceptionCode.INVALID_INPUT,
);
}
}