import * as v from 'valibot'; import { authorizationServerMetadataSchema, tokenSchema, userInfoSchema } from '../entities'; import { ICESHRIMP_NET } from '../features'; import type { PlApiBaseClient } from '../client-base'; import type { GetTokenParams, MfaChallengeParams, OauthAuthorizeParams, RevokeTokenParams, } from '../params/oauth'; type EmptyObject = Record; const oauth = (client: PlApiBaseClient) => ({ /** * Authorize a user * Displays an authorization form to the user. If approved, it will create and return an authorization code, then redirect to the desired `redirect_uri`, or show the authorization code if `urn:ietf:wg:oauth:2.0:oob` was requested. The authorization code can be used while requesting a token to obtain access to user-level methods. * @see {@link https://docs.joinmastodon.org/methods/oauth/#authorize} */ authorize: async (params: OauthAuthorizeParams) => { const response = await client.request('/oauth/authorize', { params, contentType: '' }); return v.parse(v.string(), response.json); }, /** * Obtain a token * Obtain an access token, to be used during API calls that are not public. * @see {@link https://docs.joinmastodon.org/methods/oauth/#token} */ getToken: async (params: GetTokenParams) => { if (client.features.version.software === ICESHRIMP_NET && params.grant_type === 'password') { const loginResponse = ( await client.request<{ token: string; }>('/api/iceshrimp/auth/login', { method: 'POST', body: { username: params.username, password: params.password, }, }) ).json; client.setIceshrimpAccessToken(loginResponse.token); const mastodonTokenResponse = ( await client.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, }); } const response = await client.request('/oauth/token', { method: 'POST', body: params, contentType: '', }); return v.parse(tokenSchema, { scope: params.scope || '', ...response.json }); }, /** * Revoke a token * Revoke an access token to make it no longer valid for use. * @see {@link https://docs.joinmastodon.org/methods/oauth/#revoke} */ revokeToken: async (params: RevokeTokenParams) => { const response = await client.request('/oauth/revoke', { method: 'POST', body: params, contentType: '', }); client.socket?.close(); return response.json; }, /** * Retrieve user information * Retrieves standardised OIDC claims about the currently authenticated user. * see {@link https://docs.joinmastodon.org/methods/oauth/#userinfo} */ userinfo: async () => { const response = await client.request('/oauth/userinfo'); return v.parse(userInfoSchema, response.json); }, authorizationServerMetadata: async () => { const response = await client.request('/.well-known/oauth-authorization-server'); return v.parse(authorizationServerMetadataSchema, response.json); }, /** * Get a new captcha * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromacaptcha} */ getCaptcha: async () => { const response = await client.request('/api/pleroma/captcha'); return v.parse( v.intersect([ v.object({ type: v.string(), }), v.record(v.string(), v.any()), ]), response.json, ); }, mfaChallenge: async (params: MfaChallengeParams) => { const response = await client.request('/oauth/mfa/challenge', { method: 'POST', body: params }); return v.parse(tokenSchema, response.json); }, }); export { oauth };