feat(server): support access token (#13372)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced user access tokens, enabling users to generate, list, and
revoke personal access tokens via the GraphQL API.
* Added GraphQL mutations and queries for managing access tokens,
including token creation (with optional expiration), listing, and
revocation.
* Implemented authentication support for private API endpoints using
access tokens in addition to session cookies.

* **Bug Fixes**
  * None.

* **Tests**
* Added comprehensive tests for access token creation, listing,
revocation, expiration handling, and authentication using tokens.

* **Chores**
* Updated backend models, schema, and database migrations to support
access token functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Yii
2025-07-31 13:55:10 +08:00
committed by GitHub
parent feb42e34be
commit 49e8f339d4
21 changed files with 564 additions and 9 deletions

View File

@@ -0,0 +1,9 @@
mutation generateUserAccessToken($input: GenerateAccessTokenInput!) {
generateUserAccessToken(input: $input) {
id
name
token
createdAt
expiresAt
}
}

View File

@@ -0,0 +1,8 @@
query listUserAccessTokens {
accessTokens {
id
name
createdAt
expiresAt
}
}

View File

@@ -0,0 +1,3 @@
mutation revokeUserAccessToken($id: String!) {
revokeUserAccessToken(id: $id)
}

View File

@@ -70,6 +70,41 @@ export const licenseBodyFragment = `fragment licenseBody on License {
validatedAt
variant
}`;
export const generateUserAccessTokenMutation = {
id: 'generateUserAccessTokenMutation' as const,
op: 'generateUserAccessToken',
query: `mutation generateUserAccessToken($input: GenerateAccessTokenInput!) {
generateUserAccessToken(input: $input) {
id
name
token
createdAt
expiresAt
}
}`,
};
export const listUserAccessTokensQuery = {
id: 'listUserAccessTokensQuery' as const,
op: 'listUserAccessTokens',
query: `query listUserAccessTokens {
accessTokens {
id
name
createdAt
expiresAt
}
}`,
};
export const revokeUserAccessTokenMutation = {
id: 'revokeUserAccessTokenMutation' as const,
op: 'revokeUserAccessToken',
query: `mutation revokeUserAccessToken($id: String!) {
revokeUserAccessToken(id: $id)
}`,
};
export const adminServerConfigQuery = {
id: 'adminServerConfigQuery' as const,
op: 'adminServerConfig',

View File

@@ -37,6 +37,14 @@ export interface Scalars {
Upload: { input: File; output: File };
}
export interface AccessToken {
__typename?: 'AccessToken';
createdAt: Scalars['DateTime']['output'];
expiresAt: Maybe<Scalars['DateTime']['output']>;
id: Scalars['String']['output'];
name: Scalars['String']['output'];
}
export interface AddContextBlobInput {
blobId: Scalars['String']['input'];
contextId: Scalars['String']['input'];
@@ -985,6 +993,11 @@ export interface ForkChatSessionInput {
workspaceId: Scalars['String']['input'];
}
export interface GenerateAccessTokenInput {
expiresAt?: InputMaybe<Scalars['DateTime']['input']>;
name: Scalars['String']['input'];
}
export interface GrantDocUserRolesInput {
docId: Scalars['String']['input'];
role: DocRole;
@@ -1396,6 +1409,7 @@ export interface Mutation {
/** Create a chat session */
forkCopilotSession: Scalars['String']['output'];
generateLicenseKey: Scalars['String']['output'];
generateUserAccessToken: RevealedAccessToken;
grantDocUserRoles: Scalars['Boolean']['output'];
grantMember: Scalars['Boolean']['output'];
/** import users */
@@ -1443,6 +1457,7 @@ export interface Mutation {
revokePublicDoc: DocType;
/** @deprecated use revokePublicDoc instead */
revokePublicPage: DocType;
revokeUserAccessToken: Scalars['Boolean']['output'];
sendChangeEmail: Scalars['Boolean']['output'];
sendChangePasswordEmail: Scalars['Boolean']['output'];
sendSetPasswordEmail: Scalars['Boolean']['output'];
@@ -1650,6 +1665,10 @@ export interface MutationGenerateLicenseKeyArgs {
sessionId: Scalars['String']['input'];
}
export interface MutationGenerateUserAccessTokenArgs {
input: GenerateAccessTokenInput;
}
export interface MutationGrantDocUserRolesArgs {
input: GrantDocUserRolesInput;
}
@@ -1790,6 +1809,10 @@ export interface MutationRevokePublicPageArgs {
workspaceId: Scalars['String']['input'];
}
export interface MutationRevokeUserAccessTokenArgs {
id: Scalars['String']['input'];
}
export interface MutationSendChangeEmailArgs {
callbackUrl: Scalars['String']['input'];
email?: InputMaybe<Scalars['String']['input']>;
@@ -2094,6 +2117,7 @@ export interface PublicUserType {
export interface Query {
__typename?: 'Query';
accessTokens: Array<AccessToken>;
/** get the whole app configuration */
appConfig: Scalars['JSONObject']['output'];
/** Apply updates to a doc using LLM and return the merged markdown. */
@@ -2288,6 +2312,15 @@ export interface ReplyUpdateInput {
id: Scalars['ID']['input'];
}
export interface RevealedAccessToken {
__typename?: 'RevealedAccessToken';
createdAt: Scalars['DateTime']['output'];
expiresAt: Maybe<Scalars['DateTime']['output']>;
id: Scalars['String']['output'];
name: Scalars['String']['output'];
token: Scalars['String']['output'];
}
export interface RevokeDocUserRoleInput {
docId: Scalars['String']['input'];
userId: Scalars['String']['input'];
@@ -3010,6 +3043,46 @@ export interface TokenType {
token: Scalars['String']['output'];
}
export type GenerateUserAccessTokenMutationVariables = Exact<{
input: GenerateAccessTokenInput;
}>;
export type GenerateUserAccessTokenMutation = {
__typename?: 'Mutation';
generateUserAccessToken: {
__typename?: 'RevealedAccessToken';
id: string;
name: string;
token: string;
createdAt: string;
expiresAt: string | null;
};
};
export type ListUserAccessTokensQueryVariables = Exact<{
[key: string]: never;
}>;
export type ListUserAccessTokensQuery = {
__typename?: 'Query';
accessTokens: Array<{
__typename?: 'AccessToken';
id: string;
name: string;
createdAt: string;
expiresAt: string | null;
}>;
};
export type RevokeUserAccessTokenMutationVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type RevokeUserAccessTokenMutation = {
__typename?: 'Mutation';
revokeUserAccessToken: boolean;
};
export type AdminServerConfigQueryVariables = Exact<{ [key: string]: never }>;
export type AdminServerConfigQuery = {
@@ -6184,6 +6257,11 @@ export type GrantWorkspaceTeamMemberMutation = {
};
export type Queries =
| {
name: 'listUserAccessTokensQuery';
variables: ListUserAccessTokensQueryVariables;
response: ListUserAccessTokensQuery;
}
| {
name: 'adminServerConfigQuery';
variables: AdminServerConfigQueryVariables;
@@ -6536,6 +6614,16 @@ export type Queries =
};
export type Mutations =
| {
name: 'generateUserAccessTokenMutation';
variables: GenerateUserAccessTokenMutationVariables;
response: GenerateUserAccessTokenMutation;
}
| {
name: 'revokeUserAccessTokenMutation';
variables: RevokeUserAccessTokenMutationVariables;
response: RevokeUserAccessTokenMutation;
}
| {
name: 'createChangePasswordUrlMutation';
variables: CreateChangePasswordUrlMutationVariables;