From 2445336ec0c5bd1b7f9bc823d31f2d10cb296cba Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Jun 2026 17:54:41 +0000 Subject: [PATCH] refactor(atproto): Remove unused oauth implementation Oauth will need a larger, ground-up rewrite if/when its eventually done. Remove the old, unusued implementation for now to reduce complexity. --- .../vendor/atproto/ATProtoOauthApiClient.ts | 100 ------------------ .../common/vendor/teal/TealApiClient.ts | 3 +- src/backend/scrobblers/TealfmScrobbler.ts | 3 +- src/backend/server/auth.ts | 71 ------------- src/backend/sources/TealfmSource.ts | 4 +- 5 files changed, 3 insertions(+), 178 deletions(-) delete mode 100644 src/backend/common/vendor/atproto/ATProtoOauthApiClient.ts diff --git a/src/backend/common/vendor/atproto/ATProtoOauthApiClient.ts b/src/backend/common/vendor/atproto/ATProtoOauthApiClient.ts deleted file mode 100644 index 57a7d856..00000000 --- a/src/backend/common/vendor/atproto/ATProtoOauthApiClient.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AbstractApiOptions } from "../../infrastructure/Atomic.js"; -import { TealClientData } from "../../infrastructure/config/client/tealfm.js"; -import { - NodeOAuthClient, - NodeSavedStateStore, - NodeSavedSessionStore, - type OAuthClientMetadataInput, - OAuthSession, -} from "@atproto/oauth-client-node"; -import { Agent } from "@atproto/api"; -import { ATProtoAuthenticatedApiClient } from "./ATProtoAuthenticatedApiClient.js"; - -export class ATProtoOauthApiClient extends ATProtoAuthenticatedApiClient { - - declare config: TealClientData; - - oauthClient?: NodeOAuthClient; - oauthSession: OAuthSession; - - - constructor(name: any, config: TealClientData, options: AbstractApiOptions) { - super(name, config, options); - this.logger.verbose('Will use oauth for session'); - } - - initClient = async () => { - const sessionStore: NodeSavedSessionStore = { - set: (k: string, state) => this.cache.cacheAuth.set(`session-${this.name}-${k}`, state).then(() => null), - get: (k: string) => this.cache.cacheAuth.get(`session-${this.name}-${k}`), - del: (k: string) => this.cache.cacheAuth.delete(`session-${this.name}-${k}`).then(() => null) - } - - const stateStore: NodeSavedStateStore = { - set: (k: string, state) => this.cache.cacheAuth.set(`state-${this.name}-${k}`, state).then(() => null), - get: (k: string) => this.cache.cacheAuth.get(`state-${this.name}-${k}`), - del: (k: string) => this.cache.cacheAuth.delete(`state-${this.name}-${k}`).then(() => null) - } - - try { - this.oauthClient = new NodeOAuthClient({ - clientMetadata: this.getMetadata(), - stateStore, - sessionStore - }); - } catch (e) { - throw new Error('Could not build oauth client', { cause: e }); - } - } - - restoreSession = async (): Promise => { - const did = await this.cache.cacheAuth.get(`did-${this.name}`); - if (did === undefined) { - this.logger.debug('No did has been stored yet'); - return false; - } - try { - this.oauthSession = await this.oauthClient.restore(did); - return true; - } catch (e) { - this.logger.warn(new Error('Could not restore oauth session', { cause: e })); - return false; - } - } - - - createAuthorizeUrl = async (handle: string) => { - const url = await this.oauthClient.authorize(handle.replace('@', '')); - return url.toString(); - } - - handleCallback = async (params: URLSearchParams): Promise => { - const { session } = await this.oauthClient.callback(params); - this.oauthSession = session; - this.agent = new Agent(session); - await this.cache.cacheAuth.set(`did-${this.name}`, session.did); - return true; - } - - getMetadata() { - return generateMetadata(this.name, this.config.baseUri); - } - -} - -export const generateMetadata = (name, baseUrl): OAuthClientMetadataInput => { - return { - client_name: name, - client_id: `${baseUrl}/client-metadata.json`, - client_uri: `${baseUrl}`, - redirect_uris: [`${baseUrl}/oauth/callback`], - policy_uri: `${baseUrl}/policy`, - tos_uri: `${baseUrl}/tos`, - scope: "atproto transition:generic", - grant_types: ["authorization_code", "refresh_token"], - response_types: ["code"], - application_type: "web", - token_endpoint_auth_method: "none", - dpop_bound_access_tokens: true, - }; -} \ No newline at end of file diff --git a/src/backend/common/vendor/teal/TealApiClient.ts b/src/backend/common/vendor/teal/TealApiClient.ts index debe4e04..3300bfa4 100644 --- a/src/backend/common/vendor/teal/TealApiClient.ts +++ b/src/backend/common/vendor/teal/TealApiClient.ts @@ -8,7 +8,6 @@ import { AbstractApiOptions, PagelessListensTimeRangeOptions, PagelessTimeRangeL import { ListRecord, RecordOptions, TealClientData } from "../../infrastructure/config/client/tealfm.js"; import AbstractApiClient from "../AbstractApiClient.js"; import { ATProtoAppApiClient } from "../atproto/ATProtoAppApiClient.js"; -import { ATProtoOauthApiClient } from "../atproto/ATProtoOauthApiClient.js"; import { Duration } from "dayjs/plugin/duration.js"; import { FmTealAlphaActorStatus, FmTealAlphaFeedPlay } from "./lexicons/index.js"; import { ScrobbleSubmitError } from "../../errors/MSErrors.js"; @@ -34,7 +33,7 @@ export class TealApiClient extends AbstractApiClient implements PagelessTimeRang if(config.appPassword !== undefined) { this.client = new ATProtoAppApiClient(name, config, {...options, logger: this.logger}); } else if(config.baseUri !== undefined) { - this.client = new ATProtoOauthApiClient(name, config, {...options, logger: this.logger}); + throw new Error('Oauth is not yet implemented'); } else { throw new Error(`Must define either 'baseUri' or 'appPassword' in configuration!`); } diff --git a/src/backend/scrobblers/TealfmScrobbler.ts b/src/backend/scrobblers/TealfmScrobbler.ts index de38ab5d..bca55cac 100644 --- a/src/backend/scrobblers/TealfmScrobbler.ts +++ b/src/backend/scrobblers/TealfmScrobbler.ts @@ -15,7 +15,6 @@ import { Notifiers } from "../notifier/Notifiers.js"; import { nowPlayingUpdateByPlayDuration, shouldClearNPStatus } from "./AbstractScrobbleClient.js"; import { TealClientConfig } from "../common/infrastructure/config/client/tealfm.js"; import { ATProtoAppApiClient } from "../common/vendor/atproto/ATProtoAppApiClient.js"; -import { ATProtoOauthApiClient } from "../common/vendor/atproto/ATProtoOauthApiClient.js"; import { playToRecord, TealApiClient } from "../common/vendor/teal/TealApiClient.js"; import { playToStatusRecord } from "../common/vendor/teal/TealApiClient.js"; import { nowPlayingExpirationDuration } from "../common/vendor/teal/TealApiClient.js"; @@ -85,7 +84,7 @@ export default class TealScrobbler extends AbstractHistoricalScrobbleClient { } async getAuthorizeUrl(): Promise { - return await (this.client.client as ATProtoOauthApiClient).createAuthorizeUrl(this.config.data.identifier); + throw new Error('Oauth is not yet implemented'); } doAuthentication = async () => { diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index 643e3e83..be3cc035 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -8,11 +8,6 @@ import LastfmSource from "../sources/LastfmSource.js"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import SpotifySource from "../sources/SpotifySource.js"; import YTMusicSource from "../sources/YTMusicSource.js"; -import { sortAndDeduplicateDiagnostics } from "typescript"; -import { source } from "common-tags"; -import TealScrobbler from "../scrobblers/TealfmScrobbler.js"; -import { parseRegexSingle } from "@foxxmd/regex-buddy-core"; -import { ATProtoOauthApiClient } from "../common/vendor/atproto/ATProtoOauthApiClient.js"; import LibrefmScrobbler from "../scrobblers/LibrefmScrobbler.js"; import LibrefmSource from "../sources/LibrefmSource.js"; import e from "express"; @@ -154,70 +149,4 @@ export const setupAuthRoutes = (app: Express, logger: Logger, sourceMiddle: Expr return res.send(responseContent); } }); - - app.get(/(\/api\/tealfm\/.*)/, async function (req, res) { - - const clients = scrobbleClients.getByType('tealfm') as TealScrobbler[]; - if (clients.length === 0) { - logger.warn('Received callback to Teal OAuth but no TealFM scrobble clients are configured'); - } - - // const { - // query: { - // state - // } = {} - // } = req; - - // const name = (state as string).replace('tfm',''); - - // const validClient = clients.find(x => x.name === name); - - // if (validClient === undefined) { - // logger.warn(`No Tealfm scrobble matched => URL: ${req.originalUrl} | State: ${state}`); - // } - - const intents = getTealUrlIntent(req.originalUrl); - - if(intents === undefined) { - logger.warn(`Tealfm url was not formed correctly. Should be '/api/tealfm/SCROBBLER_NAME/SOME_ACTION' but found ${req.originalUrl}`); - return res.status(404); - } - - const validClient = clients.find(x => x.name === intents[0]); - if(validClient === undefined) { - logger.warn(`No Tealfm client found with the name ${intents[0]}. Url: ${req.originalUrl}`); - return res.status(404); - } - - if(intents[1].includes('login')) { - const { - query: { - handle - } = {} - } = req; - const url = await (validClient.client.client as ATProtoOauthApiClient).createAuthorizeUrl(handle as string); - res.redirect(url) - } - - if(intents[1].includes('client-metadata.json')) { - return res.json((validClient.client.client as ATProtoOauthApiClient).getMetadata()); - } - - if(intents[1].includes('oauth/callback')) { - const result = await (validClient.client.client as ATProtoOauthApiClient).handleCallback(new URLSearchParams(req.query as Record)); - if(result) { - return res.status(200); - } - return res.status(500); - } - }); -} - -const TEAL_NAME_REGEX = new RegExp(/\/api\/tealfm\/([^\/])\/(.*)/); -const getTealUrlIntent = (url: string): [string, string] | undefined => { - const res = parseRegexSingle(TEAL_NAME_REGEX, url); - if(res === undefined) { - return undefined; - } - return res.groups as [string, string]; } diff --git a/src/backend/sources/TealfmSource.ts b/src/backend/sources/TealfmSource.ts index ab85194a..eb436ea5 100644 --- a/src/backend/sources/TealfmSource.ts +++ b/src/backend/sources/TealfmSource.ts @@ -4,12 +4,10 @@ import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; -import { AbstractATProtoApiClient } from "../common/vendor/atproto/AbstractATProtoApiClient.js"; -import { listRecordToPlay, TealApiClient } from "../common/vendor/teal/TealApiClient.js"; +import { TealApiClient } from "../common/vendor/teal/TealApiClient.js"; import { recordToPlay } from "../common/vendor/teal/TealApiClient.js"; import { TealSourceConfig } from "../common/infrastructure/config/source/tealfm.js"; import { ATProtoAppApiClient } from "../common/vendor/atproto/ATProtoAppApiClient.js"; -import { ATProtoOauthApiClient } from "../common/vendor/atproto/ATProtoOauthApiClient.js"; import { parseArrayFromMaybeString } from "../utils/StringUtils.js"; export default class TealfmSource extends MemorySource { -- 2.51.2