From 817f1b083de539fbd763593bf8564f2a57236645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicole=20Miko=C5=82ajczyk?= Date: Wed, 21 May 2025 18:38:06 +0200 Subject: [PATCH] pl-api: allow username+password log in with iceshrimp.net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicole Mikołajczyk --- packages/pl-api/lib/client.ts | 63 ++++++++++++++++++++++++- packages/pl-api/lib/directory-client.ts | 2 + packages/pl-api/lib/params/oauth.ts | 2 + packages/pl-api/lib/request.ts | 8 ++-- packages/pl-api/package.json | 2 +- packages/pl-fe/package.json | 2 +- packages/pl-fe/src/actions/oauth.ts | 3 +- packages/pl-fe/src/utils/scopes.ts | 3 ++ packages/pl-fe/yarn.lock | 8 ++-- 9 files changed, 81 insertions(+), 12 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 61fbddab3..fb076a340 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -281,6 +281,7 @@ class PlApiClient { baseURL: string; #accessToken?: string; + #iceshrimpAccessToken?: string; #instance: Instance = v.parse(instanceSchema, {}); public request = request.bind(this) as typeof request; public features: Features = getFeatures(this.#instance); @@ -505,9 +506,49 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/oauth/#token} */ getToken: async (params: GetTokenParams) => { - const response = await this.request('/oauth/token', { method: 'POST', body: params }); + if (this.features.version.software === ICESHRIMP_NET && params.grant_type === 'password') { + const loginResponse = (await this.request<{ + token: string; + }>('/api/iceshrimp/auth/login', { + method: 'POST', + body: { + username: params.username, + password: params.password, + } + })).json; + this.#iceshrimpAccessToken = loginResponse.token; - return v.parse(tokenSchema, { scope: params.scope || '', ...response.json }); + const mastodonTokenResponse = (await this.request<{ + id: string; + token: string; + created_at: string; + scopes: Array; + }>('/api/iceshrimp/sessions/mastodon', { + method: 'POST', + body: { + appName: params.client_id, + scopes: params.scope?.split(' '), + flags: { + supportsHtmlFormatting: true, + autoDetectQuotes: false, + isPleroma: true, + supportsInlineMedia: true, + }, + }, + })).json; + + return v.parse(tokenSchema, { + access_token: mastodonTokenResponse.token, + token_type: 'Bearer', + scope: mastodonTokenResponse.scopes.join(' '), + created_at: new Date(mastodonTokenResponse.created_at).getTime(), + id: mastodonTokenResponse.id, + }); + } else { + const response = await this.request('/oauth/token', { method: 'POST', body: params }); + + return v.parse(tokenSchema, { scope: params.scope || '', ...response.json }); + } }, /** @@ -1850,6 +1891,12 @@ class PlApiClient { return v.parse(v.object({ frontend_name: v.string() }), response.json); }, + + authorizeIceshrimp: async () => { + const response = await this.request('/api/v1/accounts/authorize_iceshrimp', { method: 'POST' }); + + return response.json; + }, }; public readonly filtering = { @@ -5724,6 +5771,12 @@ class PlApiClient { this.features = getFeatures(this.#instance); }; + #getIceshrimpAccessToken = async () => { + if (this.features.version.software === ICESHRIMP_NET) { + this.#iceshrimpAccessToken = await this.settings.authorizeIceshrimp(); + } + } + get accessToken(): string | undefined { return this.#accessToken; } @@ -5733,6 +5786,12 @@ class PlApiClient { this.#socket?.close(); this.#accessToken = accessToken; + + this.#getIceshrimpAccessToken(); + } + + get iceshrimpAccessToken(): string | undefined { + return this.#iceshrimpAccessToken; } get instanceInformation() { diff --git a/packages/pl-api/lib/directory-client.ts b/packages/pl-api/lib/directory-client.ts index fb7ff4835..13ddba266 100644 --- a/packages/pl-api/lib/directory-client.ts +++ b/packages/pl-api/lib/directory-client.ts @@ -28,6 +28,8 @@ class PlApiDirectoryClient { /** Unused. */ accessToken: string | undefined = undefined; + /** Unused. */ + iceshrimpAccessToken: string | undefined = undefined; /** * Server directory URL. */ diff --git a/packages/pl-api/lib/params/oauth.ts b/packages/pl-api/lib/params/oauth.ts index b912d936f..354540f8a 100644 --- a/packages/pl-api/lib/params/oauth.ts +++ b/packages/pl-api/lib/params/oauth.ts @@ -32,6 +32,8 @@ interface GetTokenParams { redirect_uri: string; /** String. List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). If `code` was provided, then this must be equal to the `scope` requested from the user. Otherwise, it must be a subset of `scopes` declared during app registration. If not provided, defaults to `read`. */ scope?: string; + username?: string; + password?: string; } /** diff --git a/packages/pl-api/lib/request.ts b/packages/pl-api/lib/request.ts index a9dbbec9b..edaec5de4 100644 --- a/packages/pl-api/lib/request.ts +++ b/packages/pl-api/lib/request.ts @@ -42,7 +42,7 @@ interface RequestBody> { type RequestMeta = Pick; -function request(this: Pick, input: URL | RequestInfo, { +function request(this: Pick, input: URL | RequestInfo, { body, method = body ? 'POST' : 'GET', params, @@ -51,10 +51,12 @@ function request(this: Pick, in contentType = 'application/json', idempotencyKey, }: RequestBody = {}) { - const fullPath = buildFullPath(input.toString(), this.baseURL, params); + input = input.toString(); + const fullPath = buildFullPath(input, this.baseURL, params); const headers = new Headers(); - if (this.accessToken) headers.set('Authorization', `Bearer ${this.accessToken}`); + if (input.startsWith('/api/iceshrimp/') && this.iceshrimpAccessToken) headers.set('Authorization', `Bearer ${this.iceshrimpAccessToken}`); + else if (this.accessToken) headers.set('Authorization', `Bearer ${this.accessToken}`); if (contentType !== '' && body) headers.set('Content-Type', contentType); if (idempotencyKey) headers.set('Idempotency-Key', contentType); diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index a738017aa..29b04a550 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -1,6 +1,6 @@ { "name": "pl-api", - "version": "1.0.0-rc.61", + "version": "1.0.0-rc.62", "type": "module", "homepage": "https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-api", "repository": { diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index a73b8c955..95b0f27d3 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -104,7 +104,7 @@ "multiselect-react-dropdown": "^2.0.25", "mutative": "^1.1.0", "path-browserify": "^1.0.1", - "pl-api": "^1.0.0-rc.61", + "pl-api": "^1.0.0-rc.62", "postcss": "^8.5.3", "process": "^0.11.10", "punycode": "^2.1.1", diff --git a/packages/pl-fe/src/actions/oauth.ts b/packages/pl-fe/src/actions/oauth.ts index 70b6caf03..ab4249155 100644 --- a/packages/pl-fe/src/actions/oauth.ts +++ b/packages/pl-fe/src/actions/oauth.ts @@ -13,8 +13,9 @@ import { getBaseURL } from 'pl-fe/utils/state'; import type { AppDispatch, RootState } from 'pl-fe/store'; -const obtainOAuthToken = (params: GetTokenParams, baseURL?: string) =>{ +const obtainOAuthToken = async (params: GetTokenParams, baseURL?: string) =>{ const client = new PlApiClient(baseURL || BuildConfig.BACKEND_URL || ''); + await client.instance.getInstance(); return client.oauth.getToken(params); }; diff --git a/packages/pl-fe/src/utils/scopes.ts b/packages/pl-fe/src/utils/scopes.ts index b350f3a0f..abc4dbbb6 100644 --- a/packages/pl-fe/src/utils/scopes.ts +++ b/packages/pl-fe/src/utils/scopes.ts @@ -12,6 +12,9 @@ const getInstanceScopes = (instance: Instance, admin: boolean = true) => { let scopes; switch (v.software) { + case ICESHRIMP_NET: + scopes = 'read write follow push iceshrimp'; + break; case TOKI: scopes = 'read write follow push write:bites'; break; diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index 331485a80..ca8d3fedd 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -6863,10 +6863,10 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pl-api@^1.0.0-rc.61: - version "1.0.0-rc.61" - resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.61.tgz#0d9969c9691e77109d3c66db02c55a0d866c1429" - integrity sha512-fShtnSPaJ/EUMqExk/eV5nJHG4vfxMgQBS0Qdrqk9PNEVoWAaazkxa6nNoXDAZ0bx+39+JlS0DyOwoet5GkYRg== +pl-api@^1.0.0-rc.62: + version "1.0.0-rc.62" + resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.62.tgz#d838b1cde4b9b4d9ed986d99f503d8f460039088" + integrity sha512-X6LN7fS874czsouIwBPx/nw7EGQtscJDixxkpF3fM7tQR8Ficn7dRfJuDgzpv1MLovxqnXC3Sn9TVVWWA+TCdg== dependencies: blurhash "^2.0.5" http-link-header "^1.1.3"