feat(settings): discovery hero rollout + ephemeral playground token (#21072)
## Summary
Two intertwined streams of work:
### UI — discovery hero pattern, settings shell, AI/API redesign
- **Generalize `SettingsDiscoveryHeroCard`** and use it on Layout, Data
Model, Apps, AI, API/Webhooks, Members. Drops 4 per-page wrapper files
(`SettingsObjectCoverImage`, `SettingsLayoutCoverImage`,
`SettingsLayoutCustomizeVideoModal`,
`SettingsDataModelVisualizeVideoModal`). Each page now supplies cover
src, modal id, and tab list.
- **Modal**: swap `<video>` placeholder for the Vimeo iframe pattern
from `twenty-docs`, per-tab `vimeoId`. Drop the parallel border-bottom
on the header (TabList draws its own baseline) and the grey background
behind the video. Note: Vimeo's embed allowlist applies — the iframes
load with the correct URL on `localhost` but the player itself requires
the video owner to allow the dev/staging domains in Vimeo settings.
- **AI page** rebuilt into a Cockpit pattern (Overview / Models / Skills
/ Tools / Usage). New `SettingsAiOverviewTab` with default Smart/Fast
pickers, at-a-glance stats, and an MCP signpost that deep-links to
`/settings/api-webhooks#mcp`. System Prompt link moved under Models.
Advanced tab removed.
- **API & Webhooks** now has 4 tabs (Playground / MCP / API Keys /
Webhooks). Hero card above tabs. Playground tab inverted to "Core API" /
"Metadata API" sections, each containing REST + GraphQL cards — schema
is the meaningful axis, protocol is secondary. Hash deep-link sync
delegated to the shared `TabListFromUrlOptionalEffect`.
- **Settings shell**: unified drawer outer padding (kill `isSettings`
branch), extract `CollapsibleNavigationDrawerSection`, add `iconColor`
on settings nav items, fix Exit Settings button alignment, 880px content
cap.
### Backend — strategy C: ephemeral playground token
The legacy paste-your-API-key flow is replaced by an on-demand
short-lived token scoped to the calling user's permissions. No shared
"Playground" API key to manage or revoke.
- New `JwtTokenTypeEnum.PLAYGROUND`. `PlaygroundTokenJwtPayload =
Omit<AccessTokenJwtPayload, 'type' | impersonation fields>` so any
future ACCESS claim flows through automatically.
- `AccessTokenService.generatePlaygroundToken` signs an access-shaped
JWT with `type: PLAYGROUND` and a configurable short TTL. A shared
private `resolveTokenSubject` helper parallelizes the user / workspace /
userWorkspace lookups for both generators.
- `JwtAuthStrategy.validateAccessToken` widened to accept
`AccessTokenJwtPayload | PlaygroundTokenJwtPayload`; impersonation gated
on `payload.type === ACCESS` so the union narrows without `as unknown
as` casts. The two branches in `validate()` collapse into one.
- New `PLAYGROUND_TOKEN_EXPIRES_IN` config var (default `2h`).
- New `generatePlaygroundToken` mutation (`WorkspaceAuthGuard`, no args,
returns `AuthToken`).
- Frontend `useOpenPlayground` hook centralizes mint → atom write →
navigate, with Apollo `onError` snackbar and a "use cached PLAYGROUND
token if still fresh" short-circuit (decodes via `jwt-decode`, checks
both `type` AND `exp`). Old API_KEY tokens left in localStorage from the
prior paste-form flow are rejected on `type` alone and force a re-mint —
this is what was causing the "This API Key is revoked" symptom on stale
browsers.
### Drive-by cleanups
- `PlaygroundToken` DTO removed (identical shape to `AuthToken` already
in use).
- 5 `customize-sidebar.webm` imports and the dead placeholder pipeline
removed.
## Test plan
### Discovery hero
- [ ] `/settings/layout`, `/settings/data-model`,
`/settings/applications`, `/settings/ai`, `/settings/api-webhooks`,
`/settings/members` each render the discovery hero card with its
illustration + play button + tabbed modal
- [ ] Modal tabs show the correct Vimeo embed URL per tab; aspect ratio
stays at 1440/900; no parallel border-bottom jog at the tab baseline
- [ ] AI Overview tab shows Smart/Fast model pickers + stats grid + MCP
signpost card; the MCP card lands on `/settings/api-webhooks#mcp` with
the MCP tab active
### API playground (ephemeral token)
- [ ] With an empty `playgroundApiKeyState` in localStorage, clicking
REST or GraphQL playground card opens the playground and the cached
token has `type: "PLAYGROUND"` with ~2h exp
- [ ] Clicking the card again within the freshness window does **not**
re-mint (`iat` / fingerprint stable across visits)
- [ ] Planting a fake API_KEY-shaped JWT in localStorage and clicking
the card forces a fresh mint (old token rejected on `type`)
- [ ] `GET /rest/companies?limit=1` with the cached token returns 200 +
real data
- [ ] `POST /graphql { __typename }` returns 200
### Settings shell
- [ ] Settings nav matches main app drawer padding; sections collapse;
Exit Settings button aligns with the workspace links above
- [ ] Active nav items have a right-gap (cleaner active state)
- [ ] Content area capped at 880px
### Verify
- [ ] `npx nx typecheck twenty-front` passes
- [ ] `npx nx typecheck twenty-server` passes
- [ ] `npx nx lint:diff-with-main twenty-front` passes
- [ ] `npx nx lint:diff-with-main twenty-server` passes
This commit is contained in:
@@ -14,6 +14,7 @@ import { UpdateApiKeyInput } from 'src/engine/core-modules/api-key/dtos/update-a
|
||||
import { apiKeyGraphqlApiExceptionHandler } from 'src/engine/core-modules/api-key/utils/api-key-graphql-api-exception-handler.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { RequireAccessTokenGuard } from 'src/engine/guards/require-access-token.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
@@ -58,6 +59,10 @@ export class ApiKeyResolver {
|
||||
}
|
||||
}
|
||||
|
||||
// A long-lived API key is durable credential management: only a first-person
|
||||
// session (ACCESS) may mint or alter one, so a short-lived derived token
|
||||
// (PLAYGROUND) or an API key itself cannot escalate into another.
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Mutation(() => ApiKeyEntity)
|
||||
async createApiKey(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -72,6 +77,7 @@ export class ApiKeyResolver {
|
||||
});
|
||||
}
|
||||
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Mutation(() => ApiKeyEntity, { nullable: true })
|
||||
async updateApiKey(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -89,6 +95,7 @@ export class ApiKeyResolver {
|
||||
return this.apiKeyService.update(input.id, workspace.id, updateData);
|
||||
}
|
||||
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Mutation(() => ApiKeyEntity, { nullable: true })
|
||||
async revokeApiKey(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@@ -97,6 +104,7 @@ export class ApiKeyResolver {
|
||||
return this.apiKeyService.revoke(input.id, workspace.id);
|
||||
}
|
||||
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Mutation(() => Boolean)
|
||||
async assignRoleToApiKey(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
|
||||
+6
@@ -21,6 +21,7 @@ import { ApiKeyService } from 'src/engine/core-modules/api-key/services/api-key.
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||
import { RequireAccessTokenGuard } from 'src/engine/guards/require-access-token.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionsRestApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-rest-api-exception.filter';
|
||||
@@ -54,6 +55,9 @@ export class ApiKeyController {
|
||||
return this.apiKeyService.findById(id, workspace.id);
|
||||
}
|
||||
|
||||
// Minting/altering a long-lived API key requires a first-person session
|
||||
// (ACCESS); a derived PLAYGROUND token or an API key cannot escalate here.
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Post()
|
||||
async create(
|
||||
@Body() createApiKeyDto: CreateApiKeyInput,
|
||||
@@ -70,6 +74,7 @@ export class ApiKeyController {
|
||||
});
|
||||
}
|
||||
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@@ -91,6 +96,7 @@ export class ApiKeyController {
|
||||
return this.apiKeyService.update(id, workspace.id, updateData);
|
||||
}
|
||||
|
||||
@UseGuards(RequireAccessTokenGuard)
|
||||
@Delete(':id')
|
||||
async remove(
|
||||
@Param('id') id: string,
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
} from 'src/engine/core-modules/api-key/exceptions/api-key.exception';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
import { ApiKeyService } from 'src/engine/core-modules/api-key/services/api-key.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleTargetService } from 'src/engine/metadata-modules/role-target/services/role-target.service';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ApiKeyExceptionCode,
|
||||
} from 'src/engine/core-modules/api-key/exceptions/api-key.exception';
|
||||
import { type ApiKeyToken } from 'src/engine/core-modules/auth/dto/api-key-token.dto';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { RoleTargetService } from 'src/engine/metadata-modules/role-target/services/role-target.service';
|
||||
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ConnectionProviderEntity } from 'src/engine/core-modules/application/connection-provider/connection-provider.entity';
|
||||
import { ConnectionProviderOAuthFlowService } from 'src/engine/core-modules/application/connection-provider/connection-provider-oauth-flow.service';
|
||||
import { ConnectionProviderService } from 'src/engine/core-modules/application/connection-provider/connection-provider.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
+2
-4
@@ -19,10 +19,8 @@ import { buildAppOAuthCallbackUrl } from 'src/engine/core-modules/application/co
|
||||
import { computePkceChallenge } from 'src/engine/core-modules/application/connection-provider/utils/compute-pkce-challenge.util';
|
||||
import { exchangeCodeForToken } from 'src/engine/core-modules/application/connection-provider/utils/exchange-code-for-token.util';
|
||||
import { generatePkceVerifier } from 'src/engine/core-modules/application/connection-provider/utils/generate-pkce-verifier.util';
|
||||
import {
|
||||
type AppOAuthStateJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AppOAuthStateJwtPayload } from 'src/engine/core-modules/auth/types/app-oauth-state-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
+2
-4
@@ -16,10 +16,8 @@ import {
|
||||
ApprovedAccessDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/approved-access-domain/approved-access-domain.exception';
|
||||
import { approvedAccessDomainValidator } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.validate';
|
||||
import {
|
||||
type ApprovedAccessDomainJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type ApprovedAccessDomainJwtPayload } from 'src/engine/core-modules/auth/types/approved-access-domain-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
ApprovedAccessDomainException,
|
||||
ApprovedAccessDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/approved-access-domain/approved-access-domain.exception';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ApiKeyService } from 'src/engine/core-modules/api-key/services/api-key.
|
||||
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
@@ -86,6 +87,10 @@ describe('AuthResolver', () => {
|
||||
provide: ApiKeyService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AccessTokenService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ResetPasswordService,
|
||||
useValue: {},
|
||||
|
||||
@@ -38,17 +38,16 @@ import { VerifyEmailAndGetLoginTokenDTO } from 'src/engine/core-modules/auth/dto
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import {
|
||||
AuthContextUser,
|
||||
JwtTokenTypeEnum,
|
||||
LoginTokenJwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { LoginTokenJwtPayload } from 'src/engine/core-modules/auth/types/login-token-jwt-payload.type';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { CaptchaGraphqlApiExceptionFilter } from 'src/engine/core-modules/captcha/filters/captcha-graphql-api-exception.filter';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
@@ -73,6 +72,7 @@ import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { RequireAccessTokenGuard } from 'src/engine/guards/require-access-token.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
@@ -80,6 +80,7 @@ import { PermissionsService } from 'src/engine/metadata-modules/permissions/perm
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
import { ApiKeyToken } from './dto/api-key-token.dto';
|
||||
import { AuthToken } from './dto/auth-token.dto';
|
||||
import { AuthTokens } from './dto/auth-tokens.dto';
|
||||
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||
import { LoginTokenDTO } from './dto/login-token.dto';
|
||||
@@ -112,6 +113,7 @@ export class AuthResolver {
|
||||
private renewTokenService: RenewTokenService,
|
||||
private userService: UserService,
|
||||
private apiKeyService: ApiKeyService,
|
||||
private accessTokenService: AccessTokenService,
|
||||
private resetPasswordService: ResetPasswordService,
|
||||
private loginTokenService: LoginTokenService,
|
||||
private workspaceAgnosticTokenService: WorkspaceAgnosticTokenService,
|
||||
@@ -805,6 +807,7 @@ export class AuthResolver {
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
RequireAccessTokenGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@Mutation(() => ApiKeyToken)
|
||||
@@ -819,6 +822,24 @@ export class AuthResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
RequireAccessTokenGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@Mutation(() => AuthToken)
|
||||
async generatePlaygroundToken(
|
||||
@AuthUser() user: UserEntity,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthProvider() authProvider: AuthProviderEnum,
|
||||
): Promise<AuthToken> {
|
||||
return await this.accessTokenService.generatePlaygroundToken({
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
authProvider,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => EmailPasswordResetLinkDTO)
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async emailPasswordResetLink(
|
||||
|
||||
@@ -45,10 +45,8 @@ import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import {
|
||||
AuthContextUser,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import {
|
||||
type AuthProviderWithPasswordType,
|
||||
type ExistingUserOrNewUser,
|
||||
|
||||
+56
-4
@@ -6,10 +6,8 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import {
|
||||
type JwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type JwtPayload } from 'src/engine/core-modules/auth/types/jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
import { JwtAuthStrategy } from './jwt.auth.strategy';
|
||||
@@ -944,4 +942,58 @@ describe('JwtAuthStrategy', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PLAYGROUND token validation', () => {
|
||||
// A PLAYGROUND token is access-shaped but must never impersonate. This is the
|
||||
// same payload that, as an ACCESS token with isImpersonating set but without
|
||||
// impersonation ids, throws 'Invalid or missing user workspace ID in
|
||||
// impersonation token' above; as a PLAYGROUND token it must skip the
|
||||
// impersonation path entirely and resolve to the first-person context.
|
||||
it('ignores isImpersonating and resolves first-person', async () => {
|
||||
const validUserId = 'valid-user-id';
|
||||
const validUserWorkspaceId = randomUUID();
|
||||
const validWorkspaceId = randomUUID();
|
||||
|
||||
const payload = {
|
||||
sub: validUserId,
|
||||
type: JwtTokenTypeEnum.PLAYGROUND,
|
||||
userWorkspaceId: validUserWorkspaceId,
|
||||
workspaceId: validWorkspaceId,
|
||||
isImpersonating: true,
|
||||
};
|
||||
|
||||
workspaceStore[validWorkspaceId] = new WorkspaceEntity();
|
||||
userStore[validUserId] = { id: validUserId, lastName: 'lastNameDefault' };
|
||||
|
||||
coreEntityCacheService.get.mockImplementation(
|
||||
async (keyName: string, entityId: string) => {
|
||||
if (keyName === 'workspaceEntity') {
|
||||
return workspaceStore[entityId] ?? null;
|
||||
}
|
||||
|
||||
if (keyName === 'user') {
|
||||
return userStore[entityId] ?? null;
|
||||
}
|
||||
|
||||
if (keyName === 'userWorkspaceEntity') {
|
||||
return {
|
||||
id: validUserWorkspaceId,
|
||||
user: { id: validUserId, lastName: 'lastNameDefault' },
|
||||
workspace: { id: validWorkspaceId },
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
strategy = createStrategy();
|
||||
|
||||
const result = await strategy.validate(payload as JwtPayload);
|
||||
|
||||
expect(result.impersonationContext).toBeUndefined();
|
||||
expect(result.tokenType).toBe(JwtTokenTypeEnum.PLAYGROUND);
|
||||
expect(result.userWorkspaceId).toBe(validUserWorkspaceId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+28
-9
@@ -13,15 +13,16 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import {
|
||||
type AccessTokenJwtPayload,
|
||||
type ApiKeyTokenJwtPayload,
|
||||
ApplicationAccessTokenJwtPayload,
|
||||
type AuthContext,
|
||||
type AuthContextUser,
|
||||
type JwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
type WorkspaceAgnosticTokenJwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/access-token-jwt-payload.type';
|
||||
import { type ApiKeyTokenJwtPayload } from 'src/engine/core-modules/auth/types/api-key-token-jwt-payload.type';
|
||||
import { ApplicationAccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/application-access-token-jwt-payload.type';
|
||||
import { type JwtPayload } from 'src/engine/core-modules/auth/types/jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type PlaygroundTokenJwtPayload } from 'src/engine/core-modules/auth/types/playground-token-jwt-payload.type';
|
||||
import { type WorkspaceAgnosticTokenJwtPayload } from 'src/engine/core-modules/auth/types/workspace-agnostic-token-jwt-payload.type';
|
||||
import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type';
|
||||
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
|
||||
import { JWT_SUPPORTED_VERIFY_ALGORITHMS } from 'src/engine/core-modules/jwt/constants/jwt-algorithm.constant';
|
||||
@@ -100,7 +101,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
}
|
||||
|
||||
private async validateAccessToken(
|
||||
payload: AccessTokenJwtPayload,
|
||||
payload: AccessTokenJwtPayload | PlaygroundTokenJwtPayload,
|
||||
): Promise<AuthContext> {
|
||||
let user: AuthContextUser | null = null;
|
||||
let context: AuthContext = {};
|
||||
@@ -117,7 +118,11 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.isImpersonating === true) {
|
||||
// Only ACCESS tokens can carry impersonation; PLAYGROUND is always first-person.
|
||||
if (
|
||||
payload.type === JwtTokenTypeEnum.ACCESS &&
|
||||
payload.isImpersonating === true
|
||||
) {
|
||||
context.impersonationContext = await this.validateImpersonation(payload);
|
||||
}
|
||||
|
||||
@@ -425,6 +430,17 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload): Promise<AuthContext> {
|
||||
const context = await this.dispatch(payload);
|
||||
|
||||
return {
|
||||
...context,
|
||||
tokenType: this.isLegacyApiKeyPayload(payload)
|
||||
? JwtTokenTypeEnum.API_KEY
|
||||
: payload.type,
|
||||
};
|
||||
}
|
||||
|
||||
private async dispatch(payload: JwtPayload): Promise<AuthContext> {
|
||||
// Support legacy api keys
|
||||
if (
|
||||
payload.type === JwtTokenTypeEnum.API_KEY ||
|
||||
@@ -437,7 +453,10 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
return await this.validateWorkspaceAgnosticToken(payload);
|
||||
}
|
||||
|
||||
if (payload.type === JwtTokenTypeEnum.ACCESS) {
|
||||
if (
|
||||
payload.type === JwtTokenTypeEnum.ACCESS ||
|
||||
payload.type === JwtTokenTypeEnum.PLAYGROUND
|
||||
) {
|
||||
return await this.validateAccessToken(payload);
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -224,6 +224,12 @@ describe('AccessTokenService', () => {
|
||||
it('should throw an error if user is not found', async () => {
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('1h');
|
||||
jest.spyOn(userRepository, 'findOne').mockResolvedValue(null);
|
||||
jest
|
||||
.spyOn(workspaceRepository, 'findOne')
|
||||
.mockResolvedValue({} as WorkspaceEntity);
|
||||
jest
|
||||
.spyOn(userWorkspaceRepository, 'findOne')
|
||||
.mockResolvedValue({} as UserWorkspaceEntity);
|
||||
|
||||
await expect(
|
||||
service.generateAccessToken({
|
||||
|
||||
+81
-48
@@ -15,11 +15,10 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
|
||||
import { JwtAuthStrategy } from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy';
|
||||
import {
|
||||
type AccessTokenJwtPayload,
|
||||
type AuthContext,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/access-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type PlaygroundTokenJwtPayload } from 'src/engine/core-modules/auth/types/playground-token-jwt-payload.type';
|
||||
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 { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -47,42 +46,36 @@ export class AccessTokenService {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
async generateAccessToken({
|
||||
userId,
|
||||
workspaceId,
|
||||
authProvider,
|
||||
isImpersonating,
|
||||
impersonatorUserWorkspaceId,
|
||||
impersonatedUserWorkspaceId,
|
||||
}: Omit<
|
||||
AccessTokenJwtPayload,
|
||||
'type' | 'workspaceMemberId' | 'userWorkspaceId' | 'sub'
|
||||
>): Promise<AuthToken> {
|
||||
const expiresIn = this.twentyConfigService.get('ACCESS_TOKEN_EXPIRES_IN');
|
||||
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: userId },
|
||||
});
|
||||
private async resolveTokenSubject(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<{
|
||||
user: UserEntity;
|
||||
workspace: WorkspaceEntity;
|
||||
userWorkspace: UserWorkspaceEntity;
|
||||
workspaceMemberId: string | undefined;
|
||||
}> {
|
||||
const [user, workspace, userWorkspace] = await Promise.all([
|
||||
this.userRepository.findOne({ where: { id: userId } }),
|
||||
this.workspaceRepository.findOne({ where: { id: workspaceId } }),
|
||||
this.userWorkspaceRepository.findOne({
|
||||
where: { userId, workspaceId },
|
||||
}),
|
||||
]);
|
||||
|
||||
userValidator.assertIsDefinedOrThrow(
|
||||
user,
|
||||
new AuthException('User is not found', AuthExceptionCode.INVALID_INPUT),
|
||||
);
|
||||
|
||||
let tokenWorkspaceMemberId: string | undefined;
|
||||
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: workspaceId },
|
||||
});
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
assertIsDefinedOrThrow(userWorkspace, UserWorkspaceNotFoundDefaultError);
|
||||
|
||||
let workspaceMemberId: string | undefined;
|
||||
|
||||
if (isWorkspaceActiveOrSuspended(workspace)) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
tokenWorkspaceMemberId =
|
||||
workspaceMemberId =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const workspaceMemberRepository =
|
||||
@@ -93,9 +86,7 @@ export class AccessTokenService {
|
||||
);
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
where: { userId: user.id },
|
||||
});
|
||||
|
||||
assertIsDefinedOrThrow(
|
||||
@@ -114,31 +105,73 @@ export class AccessTokenService {
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
assertIsDefinedOrThrow(userWorkspace, UserWorkspaceNotFoundDefaultError);
|
||||
return { user, workspace, userWorkspace, workspaceMemberId };
|
||||
}
|
||||
|
||||
const payloadImpersonatorUserWorkspaceId =
|
||||
isImpersonating === true ? impersonatorUserWorkspaceId : undefined;
|
||||
const payloadOriginalUserWorkspaceId =
|
||||
isImpersonating === true ? impersonatedUserWorkspaceId : undefined;
|
||||
async generateAccessToken({
|
||||
userId,
|
||||
workspaceId,
|
||||
authProvider,
|
||||
isImpersonating,
|
||||
impersonatorUserWorkspaceId,
|
||||
impersonatedUserWorkspaceId,
|
||||
}: Omit<
|
||||
AccessTokenJwtPayload,
|
||||
'type' | 'workspaceMemberId' | 'userWorkspaceId' | 'sub'
|
||||
>): Promise<AuthToken> {
|
||||
const expiresIn = this.twentyConfigService.get('ACCESS_TOKEN_EXPIRES_IN');
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
|
||||
const { user, userWorkspace, workspaceMemberId } =
|
||||
await this.resolveTokenSubject(userId, workspaceId);
|
||||
|
||||
const jwtPayload: AccessTokenJwtPayload = {
|
||||
sub: user.id,
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
workspaceMemberId: tokenWorkspaceMemberId,
|
||||
workspaceMemberId,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
type: JwtTokenTypeEnum.ACCESS,
|
||||
authProvider,
|
||||
isImpersonating: isImpersonating === true,
|
||||
impersonatorUserWorkspaceId: payloadImpersonatorUserWorkspaceId,
|
||||
impersonatedUserWorkspaceId: payloadOriginalUserWorkspaceId,
|
||||
impersonatorUserWorkspaceId:
|
||||
isImpersonating === true ? impersonatorUserWorkspaceId : undefined,
|
||||
impersonatedUserWorkspaceId:
|
||||
isImpersonating === true ? impersonatedUserWorkspaceId : undefined,
|
||||
};
|
||||
|
||||
const token = await this.jwtWrapperService.signAsyncOrThrow(jwtPayload, {
|
||||
expiresIn,
|
||||
});
|
||||
|
||||
return { token, expiresAt };
|
||||
}
|
||||
|
||||
async generatePlaygroundToken({
|
||||
userId,
|
||||
workspaceId,
|
||||
authProvider,
|
||||
}: Pick<
|
||||
PlaygroundTokenJwtPayload,
|
||||
'userId' | 'workspaceId' | 'authProvider'
|
||||
>): Promise<AuthToken> {
|
||||
const expiresIn = this.twentyConfigService.get(
|
||||
'PLAYGROUND_TOKEN_EXPIRES_IN',
|
||||
);
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
|
||||
const { user, userWorkspace, workspaceMemberId } =
|
||||
await this.resolveTokenSubject(userId, workspaceId);
|
||||
|
||||
const jwtPayload: PlaygroundTokenJwtPayload = {
|
||||
sub: user.id,
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
workspaceMemberId,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
type: JwtTokenTypeEnum.PLAYGROUND,
|
||||
authProvider,
|
||||
};
|
||||
|
||||
const token = await this.jwtWrapperService.signAsyncOrThrow(jwtPayload, {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
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 { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
+3
-5
@@ -8,11 +8,9 @@ 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 {
|
||||
type ApplicationAccessTokenJwtPayload,
|
||||
type ApplicationRefreshTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type ApplicationAccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/application-access-token-jwt-payload.type';
|
||||
import { type ApplicationRefreshTokenJwtPayload } from 'src/engine/core-modules/auth/types/application-refresh-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type 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';
|
||||
|
||||
+1
-1
@@ -3,7 +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 { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
import { LoginTokenService } from './login-token.service';
|
||||
|
||||
|
||||
+2
-4
@@ -8,10 +8,8 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import {
|
||||
type LoginTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type LoginTokenJwtPayload } from 'src/engine/core-modules/auth/types/login-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
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 { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
||||
AppTokenType,
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
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 { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
+2
-4
@@ -14,10 +14,8 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
|
||||
import {
|
||||
type RefreshTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RefreshTokenJwtPayload } from 'src/engine/core-modules/auth/types/refresh-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
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 { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { type AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto'
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
@Injectable()
|
||||
|
||||
+1
-1
@@ -2,7 +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 { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
import { TransientTokenService } from './transient-token.service';
|
||||
|
||||
|
||||
+2
-4
@@ -10,10 +10,8 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
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 {
|
||||
type TransientTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type TransientTokenJwtPayload } from 'src/engine/core-modules/auth/types/transient-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class TransientTokenService {
|
||||
|
||||
+1
-1
@@ -9,7 +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';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
describe('WorkspaceAgnosticToken', () => {
|
||||
let service: WorkspaceAgnosticTokenService;
|
||||
|
||||
+3
-5
@@ -11,11 +11,9 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
|
||||
import {
|
||||
type AuthContext,
|
||||
JwtTokenTypeEnum,
|
||||
type WorkspaceAgnosticTokenJwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type WorkspaceAgnosticTokenJwtPayload } from 'src/engine/core-modules/auth/types/workspace-agnostic-token-jwt-payload.type';
|
||||
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 { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
export type AccessTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.ACCESS;
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId?: string;
|
||||
userWorkspaceId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
isImpersonating?: boolean;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type ApiKeyTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.API_KEY;
|
||||
workspaceId: string;
|
||||
workspaceMemberId?: string;
|
||||
jti?: string;
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type AppOAuthStateJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APP_OAUTH_STATE;
|
||||
workspaceId: string;
|
||||
connectionProviderId: string;
|
||||
userId: string;
|
||||
userWorkspaceId: string;
|
||||
// 'user' = the resulting credential is private to userWorkspaceId.
|
||||
// 'workspace' = visible to anyone in the workspace.
|
||||
// Named `visibility` to disambiguate from OAuth `scopes` on the row.
|
||||
visibility: 'user' | 'workspace';
|
||||
// If set, the callback updates this existing connectedAccount row instead
|
||||
// of creating a new one (used by the UI's "Reconnect" action).
|
||||
reconnectingConnectedAccountId: string | null;
|
||||
redirectLocation: string | null;
|
||||
codeVerifier: string | null;
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type ApplicationAccessTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPLICATION_ACCESS;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
userWorkspaceId?: string;
|
||||
userId?: string;
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type ApplicationRefreshTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPLICATION_REFRESH;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
userWorkspaceId?: string;
|
||||
userId?: string;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type ApprovedAccessDomainJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPROVED_ACCESS_DOMAIN;
|
||||
workspaceId: string;
|
||||
approvedAccessDomainId: string;
|
||||
domain: string;
|
||||
};
|
||||
@@ -1,177 +1,7 @@
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { type FlatAuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type';
|
||||
import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
|
||||
export { AUTH_CONTEXT_USER_SELECT_FIELDS } from 'src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants';
|
||||
export { type FlatAuthContextUser as AuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
|
||||
export type RawAuthContext = {
|
||||
user?: FlatAuthContextUser | null | undefined;
|
||||
apiKey?: FlatApiKey | null | undefined;
|
||||
workspaceMemberId?: string;
|
||||
workspaceMember?: WorkspaceMemberWorkspaceEntity;
|
||||
workspace?: FlatWorkspace;
|
||||
application?: FlatApplication | null | undefined;
|
||||
userWorkspaceId?: string;
|
||||
userWorkspace?: FlatUserWorkspace;
|
||||
authProvider?: AuthProviderEnum;
|
||||
impersonationContext?: {
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @deprecated Use WorkspaceAuthContext instead
|
||||
export type AuthContext = RawAuthContext;
|
||||
|
||||
export type SerializableAuthContext = {
|
||||
userId?: string;
|
||||
userWorkspaceId?: string;
|
||||
workspaceMemberId?: string;
|
||||
apiKeyId?: string;
|
||||
applicationId?: string;
|
||||
};
|
||||
|
||||
export enum JwtTokenTypeEnum {
|
||||
ACCESS = 'ACCESS',
|
||||
REFRESH = 'REFRESH',
|
||||
WORKSPACE_AGNOSTIC = 'WORKSPACE_AGNOSTIC',
|
||||
LOGIN = 'LOGIN',
|
||||
FILE = 'FILE',
|
||||
API_KEY = 'API_KEY',
|
||||
REMOTE_SERVER = 'REMOTE_SERVER',
|
||||
KEY_ENCRYPTION_KEY = 'KEY_ENCRYPTION_KEY',
|
||||
APPLICATION_ACCESS = 'APPLICATION_ACCESS',
|
||||
APPLICATION_REFRESH = 'APPLICATION_REFRESH',
|
||||
APP_OAUTH_STATE = 'APP_OAUTH_STATE',
|
||||
APPROVED_ACCESS_DOMAIN = 'APPROVED_ACCESS_DOMAIN',
|
||||
}
|
||||
|
||||
type CommonPropertiesJwtPayload = {
|
||||
sub: string;
|
||||
};
|
||||
|
||||
export type FileTokenJwtPayloadLegacy = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.FILE;
|
||||
workspaceId: string;
|
||||
filename: string;
|
||||
workspaceMemberId?: string;
|
||||
noteBlockId?: string;
|
||||
attachmentId?: string;
|
||||
personId?: string;
|
||||
};
|
||||
|
||||
export type FileTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.FILE;
|
||||
workspaceId: string;
|
||||
fileId: string;
|
||||
};
|
||||
|
||||
export type LoginTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.LOGIN;
|
||||
workspaceId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
};
|
||||
|
||||
export type TransientTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.LOGIN;
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId: string;
|
||||
};
|
||||
|
||||
export type RefreshTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.REFRESH;
|
||||
workspaceId?: string | null;
|
||||
userId: string;
|
||||
jti?: string;
|
||||
authProvider?: AuthProviderEnum;
|
||||
targetedTokenType: JwtTokenTypeEnum;
|
||||
isImpersonating?: boolean;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
|
||||
export type WorkspaceAgnosticTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC;
|
||||
userId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
};
|
||||
|
||||
export type ApiKeyTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.API_KEY;
|
||||
workspaceId: string;
|
||||
workspaceMemberId?: string;
|
||||
jti?: string;
|
||||
};
|
||||
|
||||
export type ApplicationAccessTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPLICATION_ACCESS;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
userWorkspaceId?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export type ApplicationRefreshTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPLICATION_REFRESH;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
userWorkspaceId?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export type AccessTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.ACCESS;
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId?: string;
|
||||
userWorkspaceId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
isImpersonating?: boolean;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
|
||||
export type AppOAuthStateJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APP_OAUTH_STATE;
|
||||
workspaceId: string;
|
||||
connectionProviderId: string;
|
||||
userId: string;
|
||||
userWorkspaceId: string;
|
||||
// 'user' = the resulting credential is private to userWorkspaceId.
|
||||
// 'workspace' = visible to anyone in the workspace.
|
||||
// Named `visibility` to disambiguate from OAuth `scopes` on the row.
|
||||
visibility: 'user' | 'workspace';
|
||||
// If set, the callback updates this existing connectedAccount row instead
|
||||
// of creating a new one (used by the UI's "Reconnect" action).
|
||||
reconnectingConnectedAccountId: string | null;
|
||||
redirectLocation: string | null;
|
||||
codeVerifier: string | null;
|
||||
};
|
||||
|
||||
export type ApprovedAccessDomainJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.APPROVED_ACCESS_DOMAIN;
|
||||
workspaceId: string;
|
||||
approvedAccessDomainId: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
export type JwtPayload =
|
||||
| AccessTokenJwtPayload
|
||||
| ApiKeyTokenJwtPayload
|
||||
| ApplicationAccessTokenJwtPayload
|
||||
| ApplicationRefreshTokenJwtPayload
|
||||
| WorkspaceAgnosticTokenJwtPayload
|
||||
| LoginTokenJwtPayload
|
||||
| TransientTokenJwtPayload
|
||||
| RefreshTokenJwtPayload
|
||||
| FileTokenJwtPayload
|
||||
| FileTokenJwtPayloadLegacy
|
||||
| AppOAuthStateJwtPayload
|
||||
| ApprovedAccessDomainJwtPayload;
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export type CommonPropertiesJwtPayload = {
|
||||
sub: string;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type FileTokenJwtPayloadLegacy = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.FILE;
|
||||
workspaceId: string;
|
||||
filename: string;
|
||||
workspaceMemberId?: string;
|
||||
noteBlockId?: string;
|
||||
attachmentId?: string;
|
||||
personId?: string;
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type FileTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.FILE;
|
||||
workspaceId: string;
|
||||
fileId: string;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { type AccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/access-token-jwt-payload.type';
|
||||
import { type ApiKeyTokenJwtPayload } from 'src/engine/core-modules/auth/types/api-key-token-jwt-payload.type';
|
||||
import { type ApplicationAccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/application-access-token-jwt-payload.type';
|
||||
import { type ApplicationRefreshTokenJwtPayload } from 'src/engine/core-modules/auth/types/application-refresh-token-jwt-payload.type';
|
||||
import { type AppOAuthStateJwtPayload } from 'src/engine/core-modules/auth/types/app-oauth-state-jwt-payload.type';
|
||||
import { type ApprovedAccessDomainJwtPayload } from 'src/engine/core-modules/auth/types/approved-access-domain-jwt-payload.type';
|
||||
import { type FileTokenJwtPayload } from 'src/engine/core-modules/auth/types/file-token-jwt-payload.type';
|
||||
import { type FileTokenJwtPayloadLegacy } from 'src/engine/core-modules/auth/types/file-token-jwt-payload-legacy.type';
|
||||
import { type LoginTokenJwtPayload } from 'src/engine/core-modules/auth/types/login-token-jwt-payload.type';
|
||||
import { type PlaygroundTokenJwtPayload } from 'src/engine/core-modules/auth/types/playground-token-jwt-payload.type';
|
||||
import { type RefreshTokenJwtPayload } from 'src/engine/core-modules/auth/types/refresh-token-jwt-payload.type';
|
||||
import { type TransientTokenJwtPayload } from 'src/engine/core-modules/auth/types/transient-token-jwt-payload.type';
|
||||
import { type WorkspaceAgnosticTokenJwtPayload } from 'src/engine/core-modules/auth/types/workspace-agnostic-token-jwt-payload.type';
|
||||
|
||||
export type JwtPayload =
|
||||
| AccessTokenJwtPayload
|
||||
| ApiKeyTokenJwtPayload
|
||||
| ApplicationAccessTokenJwtPayload
|
||||
| ApplicationRefreshTokenJwtPayload
|
||||
| WorkspaceAgnosticTokenJwtPayload
|
||||
| LoginTokenJwtPayload
|
||||
| TransientTokenJwtPayload
|
||||
| RefreshTokenJwtPayload
|
||||
| FileTokenJwtPayload
|
||||
| FileTokenJwtPayloadLegacy
|
||||
| AppOAuthStateJwtPayload
|
||||
| ApprovedAccessDomainJwtPayload
|
||||
| PlaygroundTokenJwtPayload;
|
||||
@@ -0,0 +1,15 @@
|
||||
export enum JwtTokenTypeEnum {
|
||||
ACCESS = 'ACCESS',
|
||||
REFRESH = 'REFRESH',
|
||||
WORKSPACE_AGNOSTIC = 'WORKSPACE_AGNOSTIC',
|
||||
LOGIN = 'LOGIN',
|
||||
FILE = 'FILE',
|
||||
API_KEY = 'API_KEY',
|
||||
REMOTE_SERVER = 'REMOTE_SERVER',
|
||||
KEY_ENCRYPTION_KEY = 'KEY_ENCRYPTION_KEY',
|
||||
APPLICATION_ACCESS = 'APPLICATION_ACCESS',
|
||||
APPLICATION_REFRESH = 'APPLICATION_REFRESH',
|
||||
APP_OAUTH_STATE = 'APP_OAUTH_STATE',
|
||||
APPROVED_ACCESS_DOMAIN = 'APPROVED_ACCESS_DOMAIN',
|
||||
PLAYGROUND = 'PLAYGROUND',
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
export type LoginTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.LOGIN;
|
||||
workspaceId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { type AccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/access-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type PlaygroundTokenJwtPayload = Omit<
|
||||
AccessTokenJwtPayload,
|
||||
| 'type'
|
||||
| 'isImpersonating'
|
||||
| 'impersonatorUserWorkspaceId'
|
||||
| 'impersonatedUserWorkspaceId'
|
||||
> & {
|
||||
type: JwtTokenTypeEnum.PLAYGROUND;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type FlatAuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
import { type JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type';
|
||||
import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type RawAuthContext = {
|
||||
user?: FlatAuthContextUser | null | undefined;
|
||||
apiKey?: FlatApiKey | null | undefined;
|
||||
workspaceMemberId?: string;
|
||||
workspaceMember?: WorkspaceMemberWorkspaceEntity;
|
||||
workspace?: FlatWorkspace;
|
||||
application?: FlatApplication | null | undefined;
|
||||
userWorkspaceId?: string;
|
||||
userWorkspace?: FlatUserWorkspace;
|
||||
authProvider?: AuthProviderEnum;
|
||||
impersonationContext?: {
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
tokenType?: JwtTokenTypeEnum;
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
export type RefreshTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.REFRESH;
|
||||
workspaceId?: string | null;
|
||||
userId: string;
|
||||
jti?: string;
|
||||
authProvider?: AuthProviderEnum;
|
||||
targetedTokenType: JwtTokenTypeEnum;
|
||||
isImpersonating?: boolean;
|
||||
impersonatorUserWorkspaceId?: string;
|
||||
impersonatedUserWorkspaceId?: string;
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export type SerializableAuthContext = {
|
||||
userId?: string;
|
||||
userWorkspaceId?: string;
|
||||
workspaceMemberId?: string;
|
||||
apiKeyId?: string;
|
||||
applicationId?: string;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
|
||||
export type TransientTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.LOGIN;
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId: string;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type CommonPropertiesJwtPayload } from 'src/engine/core-modules/auth/types/common-properties-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
|
||||
export type WorkspaceAgnosticTokenJwtPayload = CommonPropertiesJwtPayload & {
|
||||
type: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC;
|
||||
userId: string;
|
||||
authProvider: AuthProviderEnum;
|
||||
};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
|
||||
export type WorkspaceAuthContextType =
|
||||
| 'system'
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
import { type ApiKeyWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
|
||||
type ApiKeyAuthContextInput = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
import { type ApplicationWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
|
||||
type ApplicationAuthContextInput = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
import { type PendingActivationUserWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
|
||||
type PendingActivationUserAuthContextInput = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
import { type SystemWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
|
||||
type SystemAuthContextInput = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/raw-auth-context.type';
|
||||
import { type UserWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
|
||||
type UserAuthContextInput = {
|
||||
|
||||
@@ -3,10 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FileTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FileTokenJwtPayload } from 'src/engine/core-modules/auth/types/file-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
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 { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { fileFolderConfigs } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { FileTokenJwtPayload } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FileTokenJwtPayload } from 'src/engine/core-modules/auth/types/file-token-jwt-payload.type';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
|
||||
export const SUPPORTED_FILE_FOLDERS = [
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { FileTokenJwtPayloadLegacy } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FileTokenJwtPayloadLegacy } from 'src/engine/core-modules/auth/types/file-token-jwt-payload-legacy.type';
|
||||
import { extractFileInfoFromRequest } from 'src/engine/core-modules/file/utils/extract-file-info-from-request.utils';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import {
|
||||
type JwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type JwtPayload } from 'src/engine/core-modules/auth/types/jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import {
|
||||
JWT_ASYMMETRIC_ALGORITHM,
|
||||
JWT_LEGACY_ALGORITHM,
|
||||
|
||||
+2
-4
@@ -16,10 +16,8 @@ import {
|
||||
} from 'src/engine/core-modules/code-interpreter/drivers/interfaces/code-interpreter-driver.interface';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import {
|
||||
type AccessTokenJwtPayload,
|
||||
JwtTokenTypeEnum,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type AccessTokenJwtPayload } from 'src/engine/core-modules/auth/types/access-token-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { CodeInterpreterService } from 'src/engine/core-modules/code-interpreter/code-interpreter.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
|
||||
@@ -365,6 +365,16 @@ export class ConfigVariables {
|
||||
@IsOptional()
|
||||
APPLICATION_REFRESH_TOKEN_EXPIRES_IN = '60d';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.TOKENS_DURATION,
|
||||
description:
|
||||
'Duration for which a playground token (in-app REST/GraphQL playground bearer) is valid',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
PLAYGROUND_TOKEN_EXPIRES_IN = '2h';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.EMAIL_SETTINGS,
|
||||
description: 'Email address used as the sender for outgoing emails',
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { createCipheriv, createHash, randomBytes } from 'crypto';
|
||||
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
|
||||
import { SimpleSecretEncryptionUtil } from './simple-secret-encryption.util';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { createDecipheriv, createHash } from 'crypto';
|
||||
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { type PlaintextString } from 'src/engine/core-modules/secret-encryption/branded-strings/plaintext-string.type';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user