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",
username: "maxz",
password: "7ld7i8vd",
authType: "password",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};

View File

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

View File

@@ -1,13 +1,13 @@
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
import type { KintoneRestAPIError } from '@kintone/rest-api-client';
import type { DomainWithPassword } from '@shared/types/domain';
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import type { KintoneRestAPIError } from "@kintone/rest-api-client";
import type { DomainWithPassword } from "@shared/types/domain";
import {
type AppResponse,
type AppDetail,
type FileContent,
type KintoneApiError,
AppCustomizeParameter,
} from '@shared/types/kintone';
} from "@shared/types/kintone";
/**
* Custom error class for Kintone API errors
@@ -17,9 +17,13 @@ export class KintoneError extends Error {
public readonly id?: string;
public readonly statusCode?: number;
constructor(message: string, apiError?: KintoneApiError, statusCode?: number) {
constructor(
message: string,
apiError?: KintoneApiError,
statusCode?: number,
) {
super(message);
this.name = 'KintoneError';
this.name = "KintoneError";
this.code = apiError?.code;
this.id = apiError?.id;
this.statusCode = statusCode;
@@ -36,22 +40,17 @@ export class KintoneClient {
constructor(domainConfig: DomainWithPassword) {
this.domain = domainConfig.domain;
const auth =
domainConfig.authType === 'api_token'
? { apiToken: domainConfig.apiToken || '' }
: {
username: domainConfig.username,
password: domainConfig.password,
};
this.client = new KintoneRestAPIClient({
baseUrl: `https://${domainConfig.domain}`,
auth,
auth: {
username: domainConfig.username,
password: domainConfig.password,
},
});
}
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;
return new KintoneError(
apiError.message,
@@ -64,7 +63,7 @@ export class KintoneClient {
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> {
@@ -81,7 +80,10 @@ export class KintoneClient {
* Get all apps with pagination support
* 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 () => {
// If pagination options provided, use them directly
if (options?.limit !== undefined || options?.offset !== undefined) {
@@ -134,19 +136,23 @@ export class KintoneClient {
return this.withErrorHandling(async () => {
const data = await this.client.file.downloadFile({ fileKey });
const buffer = Buffer.from(data);
const content = buffer.toString('base64');
const content = buffer.toString("base64");
return {
fileKey,
name: fileKey,
size: buffer.byteLength,
mimeType: 'application/octet-stream',
mimeType: "application/octet-stream",
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 () => {
const response = await this.client.file.uploadFile({
file: { name: fileName, data: content },
@@ -157,7 +163,10 @@ export class KintoneClient {
// ==================== 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 () => {
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 () => {
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) {
return {
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 { useDomainStore } from "@renderer/stores";
import { CodeViewer } from "../CodeViewer";
import { FileConfigResponse } from "@shared/types/kintone";
import { getDisplayName, getFileKey, isFileUpload } from "@shared/utils/fileDisplay";
import { FileConfigResponse, isFileResource } from "@shared/types/kintone";
import { getDisplayName, getFileKey } from "@shared/utils/fileDisplay";
const useStyles = createStyles(({ token, css }) => ({
container: css`
height: 100%;
@@ -180,7 +180,7 @@ const AppDetail: React.FC = () => {
{files.map((file, index) => {
const fileName = getDisplayName(file, type, index);
const fileKey = getFileKey(file);
const canView = isFileUpload(file);
const canView = isFileResource(file);
return (
<div

View File

@@ -4,13 +4,10 @@
*/
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 { useDomainStore } from "@renderer/stores";
import type {
CreateDomainParams,
UpdateDomainParams,
} from "@shared/types/ipc";
import type { CreateDomainParams, UpdateDomainParams } from "@shared/types/ipc";
const useStyles = createStyles(({ token, css }) => ({
form: css`
@@ -49,12 +46,9 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
name: editingDomain.name,
domain: editingDomain.domain,
username: editingDomain.username,
authType: editingDomain.authType,
apiToken: editingDomain.apiToken || "",
});
} else {
form.resetFields();
form.setFieldsValue({ authType: "password" });
}
}
}, [open, editingDomain, form]);
@@ -81,9 +75,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
name,
domain: processedDomain,
username: values.username,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
};
// Only include password if provided
@@ -104,9 +95,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
domain: processedDomain,
username: values.username,
password: values.password,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
};
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);
// Test connection with current form values
@@ -131,9 +118,7 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
const values = await form.validateFields([
"domain",
"username",
"authType",
"password",
"apiToken",
]);
// Process domain
@@ -149,9 +134,7 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
const result = await window.api.testDomainConnection({
domain: processedDomain,
username: values.username,
authType: values.authType,
password: values.authType === "password" ? values.password : undefined,
apiToken: values.authType === "api_token" ? values.apiToken : undefined,
password: values.password,
});
if (result.success) {
@@ -175,17 +158,11 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
width={520}
destroyOnClose
>
<Form
form={form}
layout="vertical"
className={styles.form}
initialValues={{ authType: "password" }}
>
<Form form={form} layout="vertical" className={styles.form}>
<Form.Item name="name" label="名称" style={{ marginTop: 8 }}>
<Input placeholder="可选,留空则使用域名" />
</Form.Item>
<Form.Item
name="domain"
label="Kintone 域名"
@@ -223,18 +200,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
<Input placeholder="登录 Kintone 的用户名" />
</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
name="password"
label="密码"
@@ -244,17 +209,6 @@ const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
placeholder={isEdit ? "留空则保持原密码" : "请输入密码"}
/>
</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 }}>
<Space style={{ width: "100%", justifyContent: "flex-end" }}>

View File

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

View File

@@ -86,7 +86,7 @@ export type FileResourceResponse = ExtractFileType<FileConfigResponse>;
export function isUrlResource(
resource: FileConfigResponse | FileConfigParameter,
): resource is UrlResourceParameter | UrlResourceResponse {
return resource.type === "URL";
return resource.type === "URL" && !!resource.url;
}
/**
@@ -96,5 +96,5 @@ export function isUrlResource(
export function isFileResource(
resource: FileConfigResponse | FileConfigParameter,
): 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
@@ -8,11 +8,11 @@ export function getDisplayName(
fileType: "js" | "css",
index: number,
): string {
if (file.type === "URL" && file.url) {
if (isUrlResource(file)) {
return extractFilenameFromUrl(file.url) || `URL ${index + 1}`;
}
if (file.type === "FILE" && file.file?.fileKey) {
if (isFileResource(file)) {
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)
*/
export function getFileKey(file: FileConfigResponse): string | undefined {
return file.type === "FILE" ? file.file?.fileKey : undefined;
return isFileResource(file) ? file.file?.fileKey : undefined;
}