remove api token auth

This commit is contained in:
2026-03-13 15:37:29 +08:00
parent 8ff555f9e4
commit 4d92085957
8 changed files with 58 additions and 116 deletions

View File

@@ -19,7 +19,6 @@ const TEST_CONFIG: DomainWithPassword = {
domain: "alicorn.cybozu.com", domain: "alicorn.cybozu.com",
username: "maxz", username: "maxz",
password: "7ld7i8vd", password: "7ld7i8vd",
authType: "password",
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };

View File

@@ -124,8 +124,6 @@ function registerCreateDomain(): void {
name: params.name, name: params.name,
domain: params.domain, domain: params.domain,
username: params.username, username: params.username,
authType: params.authType,
apiToken: params.apiToken,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
}; };
@@ -152,8 +150,6 @@ function registerUpdateDomain(): void {
name: params.name ?? existing.name, name: params.name ?? existing.name,
domain: params.domain ?? existing.domain, domain: params.domain ?? existing.domain,
username: params.username ?? existing.username, username: params.username ?? existing.username,
authType: params.authType ?? existing.authType,
apiToken: params.apiToken ?? existing.apiToken,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
@@ -219,8 +215,6 @@ function registerTestDomainConnection(): void {
domain: params.domain, domain: params.domain,
username: params.username, username: params.username,
password: params.password || "", password: params.password || "",
authType: params.authType,
apiToken: params.apiToken,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };

View File

@@ -1,13 +1,13 @@
import { KintoneRestAPIClient } from '@kintone/rest-api-client'; import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import type { KintoneRestAPIError } from '@kintone/rest-api-client'; import type { KintoneRestAPIError } from "@kintone/rest-api-client";
import type { DomainWithPassword } from '@shared/types/domain'; import type { DomainWithPassword } from "@shared/types/domain";
import { import {
type AppResponse, type AppResponse,
type AppDetail, type AppDetail,
type FileContent, type FileContent,
type KintoneApiError, type KintoneApiError,
AppCustomizeParameter, AppCustomizeParameter,
} from '@shared/types/kintone'; } from "@shared/types/kintone";
/** /**
* Custom error class for Kintone API errors * Custom error class for Kintone API errors
@@ -17,9 +17,13 @@ export class KintoneError extends Error {
public readonly id?: string; public readonly id?: string;
public readonly statusCode?: number; public readonly statusCode?: number;
constructor(message: string, apiError?: KintoneApiError, statusCode?: number) { constructor(
message: string,
apiError?: KintoneApiError,
statusCode?: number,
) {
super(message); super(message);
this.name = 'KintoneError'; this.name = "KintoneError";
this.code = apiError?.code; this.code = apiError?.code;
this.id = apiError?.id; this.id = apiError?.id;
this.statusCode = statusCode; this.statusCode = statusCode;
@@ -36,22 +40,17 @@ export class KintoneClient {
constructor(domainConfig: DomainWithPassword) { constructor(domainConfig: DomainWithPassword) {
this.domain = domainConfig.domain; this.domain = domainConfig.domain;
const auth =
domainConfig.authType === 'api_token'
? { apiToken: domainConfig.apiToken || '' }
: {
username: domainConfig.username,
password: domainConfig.password,
};
this.client = new KintoneRestAPIClient({ this.client = new KintoneRestAPIClient({
baseUrl: `https://${domainConfig.domain}`, baseUrl: `https://${domainConfig.domain}`,
auth, auth: {
username: domainConfig.username,
password: domainConfig.password,
},
}); });
} }
private convertError(error: unknown): KintoneError { private convertError(error: unknown): KintoneError {
if (error && typeof error === 'object' && 'code' in error) { if (error && typeof error === "object" && "code" in error) {
const apiError = error as KintoneRestAPIError; const apiError = error as KintoneRestAPIError;
return new KintoneError( return new KintoneError(
apiError.message, apiError.message,
@@ -64,7 +63,7 @@ export class KintoneClient {
return new KintoneError(error.message); return new KintoneError(error.message);
} }
return new KintoneError('Unknown error occurred'); return new KintoneError("Unknown error occurred");
} }
private async withErrorHandling<T>(operation: () => Promise<T>): Promise<T> { private async withErrorHandling<T>(operation: () => Promise<T>): Promise<T> {
@@ -81,7 +80,10 @@ export class KintoneClient {
* Get all apps with pagination support * Get all apps with pagination support
* Fetches all apps by making multiple requests if needed * Fetches all apps by making multiple requests if needed
*/ */
async getApps(options?: { limit?: number; offset?: number }): Promise<AppResponse[]> { async getApps(options?: {
limit?: number;
offset?: number;
}): Promise<AppResponse[]> {
return this.withErrorHandling(async () => { return this.withErrorHandling(async () => {
// If pagination options provided, use them directly // If pagination options provided, use them directly
if (options?.limit !== undefined || options?.offset !== undefined) { if (options?.limit !== undefined || options?.offset !== undefined) {
@@ -134,19 +136,23 @@ export class KintoneClient {
return this.withErrorHandling(async () => { return this.withErrorHandling(async () => {
const data = await this.client.file.downloadFile({ fileKey }); const data = await this.client.file.downloadFile({ fileKey });
const buffer = Buffer.from(data); const buffer = Buffer.from(data);
const content = buffer.toString('base64'); const content = buffer.toString("base64");
return { return {
fileKey, fileKey,
name: fileKey, name: fileKey,
size: buffer.byteLength, size: buffer.byteLength,
mimeType: 'application/octet-stream', mimeType: "application/octet-stream",
content, content,
}; };
}); });
} }
async uploadFile(content: string | Buffer, fileName: string, _mimeType?: string): Promise<{ fileKey: string }> { async uploadFile(
content: string | Buffer,
fileName: string,
_mimeType?: string,
): Promise<{ fileKey: string }> {
return this.withErrorHandling(async () => { return this.withErrorHandling(async () => {
const response = await this.client.file.uploadFile({ const response = await this.client.file.uploadFile({
file: { name: fileName, data: content }, file: { name: fileName, data: content },
@@ -157,7 +163,10 @@ export class KintoneClient {
// ==================== Deploy APIs ==================== // ==================== Deploy APIs ====================
async updateAppCustomize(appId: string, config: Omit<AppCustomizeParameter, 'app'>): Promise<void> { async updateAppCustomize(
appId: string,
config: Omit<AppCustomizeParameter, "app">,
): Promise<void> {
return this.withErrorHandling(async () => { return this.withErrorHandling(async () => {
await this.client.app.updateAppCustomize({ ...config, app: appId }); await this.client.app.updateAppCustomize({ ...config, app: appId });
}); });
@@ -169,10 +178,12 @@ export class KintoneClient {
}); });
} }
async getDeployStatus(appId: string): Promise<'PROCESSING' | 'SUCCESS' | 'FAIL' | 'CANCEL'> { async getDeployStatus(
appId: string,
): Promise<"PROCESSING" | "SUCCESS" | "FAIL" | "CANCEL"> {
return this.withErrorHandling(async () => { return this.withErrorHandling(async () => {
const response = await this.client.app.getDeployStatus({ apps: [appId] }); const response = await this.client.app.getDeployStatus({ apps: [appId] });
return response.apps[0]?.status || 'FAIL'; return response.apps[0]?.status || "FAIL";
}); });
} }
@@ -186,7 +197,8 @@ export class KintoneClient {
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: error instanceof KintoneError ? error.message : 'Connection failed', error:
error instanceof KintoneError ? error.message : "Connection failed",
}; };
} }
} }

View File

@@ -24,8 +24,8 @@ import { createStyles } from "antd-style";
import { useAppStore } from "@renderer/stores"; import { useAppStore } from "@renderer/stores";
import { useDomainStore } from "@renderer/stores"; import { useDomainStore } from "@renderer/stores";
import { CodeViewer } from "../CodeViewer"; import { CodeViewer } from "../CodeViewer";
import { FileConfigResponse } from "@shared/types/kintone"; import { FileConfigResponse, isFileResource } from "@shared/types/kintone";
import { getDisplayName, getFileKey, isFileUpload } from "@shared/utils/fileDisplay"; import { getDisplayName, getFileKey } from "@shared/utils/fileDisplay";
const useStyles = createStyles(({ token, css }) => ({ const useStyles = createStyles(({ token, css }) => ({
container: css` container: css`
height: 100%; height: 100%;
@@ -180,7 +180,7 @@ const AppDetail: React.FC = () => {
{files.map((file, index) => { {files.map((file, index) => {
const fileName = getDisplayName(file, type, index); const fileName = getDisplayName(file, type, index);
const fileKey = getFileKey(file); const fileKey = getFileKey(file);
const canView = isFileUpload(file); const canView = isFileResource(file);
return ( return (
<div <div

View File

@@ -4,13 +4,10 @@
*/ */
import React from "react"; import React from "react";
import { Modal, Form, Input, Select, Button, Space, message } from "antd"; import { Modal, Form, Input, Button, Space, message } from "antd";
import { createStyles } from "antd-style"; import { createStyles } from "antd-style";
import { useDomainStore } from "@renderer/stores"; import { useDomainStore } from "@renderer/stores";
import type { import type { CreateDomainParams, UpdateDomainParams } from "@shared/types/ipc";
CreateDomainParams,
UpdateDomainParams,
} from "@shared/types/ipc";
const useStyles = createStyles(({ token, css }) => ({ const useStyles = createStyles(({ token, css }) => ({
form: css` form: css`
@@ -49,12 +46,9 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
name: editingDomain.name, name: editingDomain.name,
domain: editingDomain.domain, domain: editingDomain.domain,
username: editingDomain.username, username: editingDomain.username,
authType: editingDomain.authType,
apiToken: editingDomain.apiToken || "",
}); });
} else { } else {
form.resetFields(); form.resetFields();
form.setFieldsValue({ authType: "password" });
} }
} }
}, [open, editingDomain, form]); }, [open, editingDomain, form]);
@@ -81,9 +75,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
name, name,
domain: processedDomain, domain: processedDomain,
username: values.username, username: values.username,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
}; };
// Only include password if provided // Only include password if provided
@@ -104,9 +95,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
domain: processedDomain, domain: processedDomain,
username: values.username, username: values.username,
password: values.password, password: values.password,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
}; };
const success = await createDomain(params); const success = await createDomain(params);
@@ -122,7 +110,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
} }
}; };
const authType = Form.useWatch("authType", form);
const [testing, setTesting] = React.useState(false); const [testing, setTesting] = React.useState(false);
// Test connection with current form values // Test connection with current form values
@@ -131,9 +118,7 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
const values = await form.validateFields([ const values = await form.validateFields([
"domain", "domain",
"username", "username",
"authType",
"password", "password",
"apiToken",
]); ]);
// Process domain // Process domain
@@ -149,9 +134,7 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
const result = await window.api.testDomainConnection({ const result = await window.api.testDomainConnection({
domain: processedDomain, domain: processedDomain,
username: values.username, username: values.username,
authType: values.authType, password: values.password,
password: values.authType === "password" ? values.password : undefined,
apiToken: values.authType === "api_token" ? values.apiToken : undefined,
}); });
if (result.success) { if (result.success) {
@@ -175,17 +158,11 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
width={520} width={520}
destroyOnClose destroyOnClose
> >
<Form <Form form={form} layout="vertical" className={styles.form}>
form={form}
layout="vertical"
className={styles.form}
initialValues={{ authType: "password" }}
>
<Form.Item name="name" label="名称" style={{ marginTop: 8 }}> <Form.Item name="name" label="名称" style={{ marginTop: 8 }}>
<Input placeholder="可选,留空则使用域名" /> <Input placeholder="可选,留空则使用域名" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="domain" name="domain"
label="Kintone 域名" label="Kintone 域名"
@@ -223,18 +200,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
<Input placeholder="登录 Kintone 的用户名" /> <Input placeholder="登录 Kintone 的用户名" />
</Form.Item> </Form.Item>
<Form.Item
name="authType"
label="认证方式"
rules={[{ required: true }]}
>
<Select>
<Select.Option value="password"></Select.Option>
<Select.Option value="api_token">API Token </Select.Option>
</Select>
</Form.Item>
{authType === "password" && (
<Form.Item <Form.Item
name="password" name="password"
label="密码" label="密码"
@@ -244,17 +209,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
placeholder={isEdit ? "留空则保持原密码" : "请输入密码"} placeholder={isEdit ? "留空则保持原密码" : "请输入密码"}
/> />
</Form.Item> </Form.Item>
)}
{authType === "api_token" && (
<Form.Item
name="apiToken"
label="API Token"
rules={[{ required: true, message: "请输入 API Token" }]}
>
<Input placeholder="从 Kintone 设置中获取 API Token" />
</Form.Item>
)}
<Form.Item style={{ marginTop: 24, marginBottom: 0 }}> <Form.Item style={{ marginTop: 24, marginBottom: 0 }}>
<Space style={{ width: "100%", justifyContent: "flex-end" }}> <Space style={{ width: "100%", justifyContent: "flex-end" }}>

View File

@@ -4,11 +4,7 @@
*/ */
import type { Domain, DomainWithStatus } from "./domain"; import type { Domain, DomainWithStatus } from "./domain";
import type { import type { AppResponse, AppDetail, FileContent } from "./kintone";
AppResponse,
AppDetail,
FileContent,
} from "./kintone";
import type { Version, DownloadMetadata, BackupMetadata } from "./version"; import type { Version, DownloadMetadata, BackupMetadata } from "./version";
// Unified result type // Unified result type
@@ -23,8 +19,6 @@ export interface CreateDomainParams {
domain: string; domain: string;
username: string; username: string;
password: string; password: string;
authType: "password" | "api_token";
apiToken?: string;
} }
export interface UpdateDomainParams { export interface UpdateDomainParams {
@@ -33,16 +27,12 @@ export interface UpdateDomainParams {
domain?: string; domain?: string;
username?: string; username?: string;
password?: string; password?: string;
authType?: "password" | "api_token";
apiToken?: string;
} }
export interface TestDomainConnectionParams { export interface TestDomainConnectionParams {
domain: string; domain: string;
username: string; username: string;
authType: "password" | "api_token"; password: string;
password?: string;
apiToken?: string;
} }
// ==================== Browse IPC Types ==================== // ==================== Browse IPC Types ====================

View File

@@ -86,7 +86,7 @@ export type FileResourceResponse = ExtractFileType<FileConfigResponse>;
export function isUrlResource( export function isUrlResource(
resource: FileConfigResponse | FileConfigParameter, resource: FileConfigResponse | FileConfigParameter,
): resource is UrlResourceParameter | UrlResourceResponse { ): resource is UrlResourceParameter | UrlResourceResponse {
return resource.type === "URL"; return resource.type === "URL" && !!resource.url;
} }
/** /**
@@ -96,5 +96,5 @@ export function isUrlResource(
export function isFileResource( export function isFileResource(
resource: FileConfigResponse | FileConfigParameter, resource: FileConfigResponse | FileConfigParameter,
): resource is FileResourceParameter | FileResourceResponse { ): resource is FileResourceParameter | FileResourceResponse {
return resource.type === "FILE"; return resource.type === "FILE" && !!resource.file?.fileKey;
} }

View File

@@ -1,4 +1,4 @@
import type { FileConfigResponse } from "@shared/types/kintone"; import { isFileResource, isUrlResource, type FileConfigResponse } from "@shared/types/kintone";
/** /**
* Get user-friendly display name for a file config * Get user-friendly display name for a file config
@@ -8,11 +8,11 @@ export function getDisplayName(
fileType: "js" | "css", fileType: "js" | "css",
index: number, index: number,
): string { ): string {
if (file.type === "URL" && file.url) { if (isUrlResource(file)) {
return extractFilenameFromUrl(file.url) || `URL ${index + 1}`; return extractFilenameFromUrl(file.url) || `URL ${index + 1}`;
} }
if (file.type === "FILE" && file.file?.fileKey) { if (isFileResource(file)) {
return `${file.file.fileKey}.${fileType}`; return `${file.file.fileKey}.${fileType}`;
} }
@@ -33,16 +33,9 @@ export function extractFilenameFromUrl(url: string): string | null {
} }
} }
/**
* Check if file config is a FILE type upload
*/
export function isFileUpload(file: FileConfigResponse): boolean {
return file.type === "FILE" && !!file.file?.fileKey;
}
/** /**
* Get fileKey from file config (only for FILE type) * Get fileKey from file config (only for FILE type)
*/ */
export function getFileKey(file: FileConfigResponse): string | undefined { export function getFileKey(file: FileConfigResponse): string | undefined {
return file.type === "FILE" ? file.file?.fileKey : undefined; return isFileResource(file) ? file.file?.fileKey : undefined;
} }