diff --git a/CHANGELOG.md b/CHANGELOG.md index 412d6e2f51..2759e4d523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Feat: 全てのチャットメッセージを既読にするAPIを追加(chat/read-all) - Fix: アカウント削除が正常に行われないことがあった問題を修正 +### Misskey.js +- Feat: MiAuth 認証ラッパー ## 2025.6.0 diff --git a/packages/misskey-js/README.md b/packages/misskey-js/README.md index 5ab2787c47..a77ec90cae 100644 --- a/packages/misskey-js/README.md +++ b/packages/misskey-js/README.md @@ -37,8 +37,44 @@ import * as Misskey from 'misskey-js'; import { api as misskeyApi } from 'misskey-js'; ``` -## Authenticate -todo +## Authenticate (MiAuth) +MiAuthでの認証に対応しています。 + +### Step 1: 認証URLを生成 +`APIClient`クラスの`getMiAuthURL`メソッドを使用して認証URLを生成します(これは同期関数です)。生成したURLにユーザーを誘導し、認可させてください。 + +``` ts +const cli = new Misskey.api.APIClient({ + origin: 'https://misskey.test', +}); + +const { url } = cli.getMiAuthURL({ + name: 'My app', + callback: 'https://example.com/callback', + permission: ['read:account'], +}); + +// URLに飛ばす(例) +location.href = url; +``` + +### Step 2: セッションIDからアクセストークンを取得 +`APIClient`クラスの`authWithMiAuth`メソッドを使用してセッションIDからアクセストークンを取得します。アクセストークンは返却されるほか、以降同一のインスタンスを利用したリクエストに自動で設定されます(この挙動は第2引数に`false`を与えることで回避できます)。 + +コールバックURLを指定した場合、セッションIDはURLパラメータの`session`から取得できます。 + +``` ts +const sessionId = new URL(location.href).searchParams.get('session'); + +const cli = new Misskey.api.APIClient({ + origin: 'https://misskey.test', +}); + +const { token } = await cli.authWithMiAuth(sessionId); + +// 以後、同じAPIClientを使い続ける場合はトークンが自動で設定されます +const i = await cli.request('i'); +``` ## API request APIを利用する際は、利用するサーバーの情報とアクセストークンを与えて`APIClient`クラスのインスタンスを初期化し、そのインスタンスの`request`メソッドを呼び出してリクエストを行います。 diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 7578275b03..51257660ea 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -521,10 +521,22 @@ class APIClient { fetch?: APIClient['fetch'] | null | undefined; }); // (undocumented) + authWithMiAuth(sessionId: string, setToken?: boolean): Promise; + // (undocumented) credential: string | null | undefined; // (undocumented) fetch: FetchLike; // (undocumented) + getMiAuthURL(options: { + name?: string; + icon?: string; + callback?: string; + permission?: typeof permissions_2[number][]; + }, sessionId?: string): { + sessionId: string; + url: string; + }; + // (undocumented) origin: string; } @@ -1463,6 +1475,7 @@ declare namespace entities { SigninWithPasskeyRequest, SigninWithPasskeyInitResponse, SigninWithPasskeyResponse, + MiAuthCheckResponse, PartialRolePolicyOverride, EmptyRequest, EmptyResponse, @@ -2758,6 +2771,12 @@ type MetaRequest = operations['meta']['requestBody']['content']['application/jso // @public (undocumented) type MetaResponse = operations['meta']['responses']['200']['content']['application/json']; +// @public (undocumented) +type MiAuthCheckResponse = { + token: string; + user: UserDetailedNotMe; +}; + // @public (undocumented) type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBody']['content']['application/json']; @@ -3748,6 +3767,7 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons // Warnings were encountered during analysis: // +// src/api.ts:135:3 - (ae-forgotten-export) The symbol "permissions_2" needs to be exported by the entry point index.d.ts // src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:218:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 1f3c856bdb..6b4909514b 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -41,17 +41,17 @@ "@types/node": "22.15.21", "@typescript-eslint/eslint-plugin": "8.32.1", "@typescript-eslint/parser": "8.32.1", + "esbuild": "0.25.4", + "execa": "8.0.1", + "glob": "11.0.2", "jest": "29.7.0", "jest-fetch-mock": "3.0.3", "jest-websocket-mock": "2.5.0", "mock-socket": "9.3.1", "ncp": "2.0.0", "nodemon": "3.1.10", - "execa": "8.0.1", "tsd": "0.32.0", - "typescript": "5.8.3", - "esbuild": "0.25.4", - "glob": "11.0.2" + "typescript": "5.8.3" }, "files": [ "built" diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts index e663d712a7..67eef49519 100644 --- a/packages/misskey-js/src/api.ts +++ b/packages/misskey-js/src/api.ts @@ -1,7 +1,9 @@ import './autogen/apiClientJSDoc.js'; import { endpointReqTypes } from './autogen/endpoint.js'; +import { permissions } from './consts.js'; import type { SwitchCaseResponseType, Endpoints } from './api.types.js'; +import type { MiAuthCheckResponse } from './entities.js'; export type { SwitchCaseResponseType, @@ -125,4 +127,55 @@ export class APIClient { }).catch(reject); }); } + + public getMiAuthURL(options: { + name?: string; + icon?: string; + callback?: string; + permission?: typeof permissions[number][]; + }, sessionId?: string): { + sessionId: string; + url: string; + } { + const params = new URLSearchParams(); + if (options.name) params.set('name', options.name); + if (options.icon) params.set('icon', options.icon); + if (options.callback) params.set('callback', options.callback); + if (options.permission) params.set('permission', options.permission.join(',')); + + const _sessionId = sessionId ?? crypto.randomUUID(); + + return { + sessionId: _sessionId, + url: `${this.origin}/miauth/${_sessionId}?${params.toString()}`, + }; + } + + public authWithMiAuth(sessionId: string, setToken = true): Promise { + return new Promise((resolve, reject) => { + this.fetch(`${this.origin}/api/miauth/${sessionId}/check`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'omit', + cache: 'no-cache', + }).then(async (res) => { + const body = res.status === 204 ? null : await res.json(); + + if (res.status === 200 && body) { + if (setToken) { + this.credential = body.token; + } + + resolve(body as MiAuthCheckResponse); + } else { + reject({ + [MK_API_ERROR]: true, + ...body.error, + }); + } + }).catch(reject); + }); + } } diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index ed1d89a685..8c3ae6dd0a 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -314,6 +314,11 @@ export type SigninWithPasskeyResponse = { signinResponse: SigninFlowResponse & { finished: true }; }; +export type MiAuthCheckResponse = { + token: string, + user: UserDetailedNotMe, +}; + type Values> = T[keyof T]; export type PartialRolePolicyOverride = Partial<{ [k in keyof RolePolicies]: Omit, 'value'> & { value: RolePolicies[k] } }>; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7159527588..898f763a03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14418,7 +14418,7 @@ snapshots: '@stylistic/eslint-plugin@2.13.0(eslint@9.27.0)(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.0(eslint@9.27.0)(typescript@5.8.3) eslint: 9.27.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 @@ -14658,7 +14658,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) fflate: 0.8.2 token-types: 6.0.0 transitivePeerDependencies: @@ -15077,7 +15077,7 @@ snapshots: '@typescript-eslint/types': 8.33.0 '@typescript-eslint/typescript-estree': 8.33.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.33.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) eslint: 9.27.0 typescript: 5.8.3 transitivePeerDependencies: @@ -15087,7 +15087,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.33.0(typescript@5.8.3) '@typescript-eslint/types': 8.33.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript @@ -15121,7 +15121,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.33.0(typescript@5.8.3) '@typescript-eslint/utils': 8.33.0(eslint@9.27.0)(typescript@5.8.3) - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) eslint: 9.27.0 ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 @@ -15152,7 +15152,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.33.0(typescript@5.8.3) '@typescript-eslint/types': 8.33.0 '@typescript-eslint/visitor-keys': 8.33.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -15205,7 +15205,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -15546,7 +15546,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true @@ -17037,7 +17037,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.25.5): dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) esbuild: 0.25.5 transitivePeerDependencies: - supports-color @@ -17679,7 +17679,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.1): optionalDependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -18077,7 +18077,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18105,7 +18105,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true @@ -18113,7 +18113,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18209,7 +18209,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -18428,7 +18428,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -19474,7 +19474,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -20863,7 +20863,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -21220,7 +21220,7 @@ snapshots: dependencies: '@hapi/hoek': 11.0.4 '@hapi/wreck': 18.0.1 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) joi: 17.13.3 transitivePeerDependencies: - supports-color @@ -21320,7 +21320,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -21417,7 +21417,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -21595,7 +21595,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.2 formidable: 3.5.4 @@ -21940,7 +21940,7 @@ snapshots: app-root-path: 3.1.0 buffer: 6.0.3 dayjs: 1.11.13 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) dedent: 1.6.0 dotenv: 16.4.7 glob: 10.4.5 @@ -22136,7 +22136,7 @@ snapshots: vite-node@3.1.4(@types/node@22.15.28)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4): dependencies: cac: 6.7.14 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@22.15.28)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4) @@ -22185,7 +22185,7 @@ snapshots: '@vitest/spy': 3.1.4 '@vitest/utils': 3.1.4 chai: 5.2.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 @@ -22272,7 +22272,7 @@ snapshots: vue-eslint-parser@10.1.3(eslint@9.27.0): dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1(supports-color@8.1.1) eslint: 9.27.0 eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0