Skip to main content
Glama

Reddit MCP Server

by ozipi
reddit-auth-service.ts9.1 kB
/** * @file Reddit authentication service * @module services/reddit/reddit-auth-service * * @remarks * This service handles OAuth2 authentication for the Reddit API. * It manages access tokens, automatic token refresh, and provides * authenticated headers for API requests. * * The service uses the refresh token grant type to obtain access tokens * without requiring user interaction after initial authorization. * * @see {@link https://github.com/reddit-archive/reddit/wiki/OAuth2} Reddit OAuth2 Documentation */ import { REDDIT_AUTH_URL } from '../../constants.js'; import type { RedditServiceConfig} from '../../types/reddit.js'; import { RedditError } from '../../types/reddit.js'; /** * Service for managing Reddit API authentication * * @remarks * This service handles: * - OAuth2 token management with automatic refresh * - User agent construction per Reddit API requirements * - Authenticated header generation for API requests * - User information and preferences fetching * * @example * ```typescript * const authService = new RedditAuthService({ * clientId: 'your-client-id', * clientSecret: 'your-client-secret', * refreshToken: 'your-refresh-token', * appName: 'MyApp', * appVersion: '1.0.0', * username: 'mybot' * }); * * await authService.initialize(); * const headers = await authService.getAuthHeaders(); * ``` */ export class RedditAuthService { private readonly tokenEndpoint = REDDIT_AUTH_URL; private readonly userAgent: string; private accessToken: string | null = null; private tokenExpiresAt: number | null = null; /** * Creates a new Reddit authentication service * * @param config - Reddit service configuration * @param config.clientId - OAuth2 client ID from Reddit app registration * @param config.clientSecret - OAuth2 client secret * @param config.refreshToken - Long-lived refresh token * @param config.appName - Application name for user agent (e.g., 'my-reddit-app') * @param config.appVersion - Application version for user agent (e.g., 'v1.0.0') * @param config.username - Developer's Reddit username for user agent compliance * * @remarks * The username parameter should be the DEVELOPER'S Reddit account username, * not the end user's. This is used to construct a User-Agent string that * complies with Reddit's API requirements. The format will be: * `<appName>/<appVersion> by /u/<username>` * * @example * ```typescript * // Correct: Using developer's username * new RedditAuthService({ * username: 'my_developer_account' // Your Reddit username * // ... other config * }); * * // Incorrect: Using end user's username * new RedditAuthService({ * username: authenticatedUser.username // ❌ Don't do this * // ... other config * }); * ``` */ constructor(private readonly config: RedditServiceConfig) { this.userAgent = `${config.appName}/${config.appVersion} by /u/${config.username}`; } /** * Initializes the authentication service by obtaining an access token * * @remarks * This method must be called before making any API requests. * It will obtain a fresh access token using the refresh token. * * @throws {RedditError} Thrown if token refresh fails */ public async initialize(): Promise<void> { await this.refreshAccessToken(); } /** * Manually sets an access token (used for OAuth2 flow) * * @param token - The OAuth2 access token * @param expiresInSeconds - Token expiration time in seconds (defaults to 3600) * * @remarks * This method is typically used when tokens are obtained through * the OAuth2 authorization code flow rather than refresh token flow. */ public setAccessToken(token: string, expiresInSeconds?: number): void { this.accessToken = token; this.tokenExpiresAt = expiresInSeconds ? Date.now() + expiresInSeconds * 1000 : Date.now() + 3600 * 1000; // Default to 1 hour } /** * Gets the authentication headers required for Reddit API requests * * @returns Headers object with Authorization, User-Agent, and Accept headers * @throws {RedditError} Thrown if unable to obtain access token * * @example * ```typescript * const headers = await authService.getAuthHeaders(); * const response = await fetch('https://oauth.reddit.com/api/v1/me', { headers }); * ``` */ public async getAuthHeaders(): Promise<Record<string, string>> { const token = await this.getAccessToken(); return { Authorization: `Bearer ${token}`, "User-Agent": this.userAgent, Accept: "application/json", }; } /** * Gets the current access token, refreshing if necessary * * @returns The valid OAuth2 access token * @throws {RedditError} Thrown if unable to obtain or refresh token * * @remarks * This method automatically handles token expiration and refresh. * Tokens are refreshed if they are expired or about to expire. */ public async getAccessToken(): Promise<string> { if (!this.accessToken || this.isTokenExpired()) { await this.refreshAccessToken(); } return this.accessToken!; } /** * Fetches information about the authenticated user * * @returns User information including karma, account age, and status * @throws {RedditError} Thrown if API request fails * * @example * ```typescript * const userInfo = await authService.fetchUserInfo(); * console.log(`Username: ${userInfo.name}`); * console.log(`Total karma: ${userInfo.link_karma + userInfo.comment_karma}`); * ``` */ public async fetchUserInfo() { const token = await this.getAccessToken(); const response = await fetch("https://oauth.reddit.com/api/v1/me", { headers: { Authorization: `Bearer ${token}`, "User-Agent": this.userAgent, }, }); if (!response.ok) { throw new RedditError(`Failed to fetch user info: ${response.statusText}`, "API_ERROR"); } const data: any = await response.json(); return { id: data.id, name: data.name, created_utc: data.created_utc, comment_karma: data.comment_karma, link_karma: data.link_karma, is_gold: data.is_gold, is_mod: data.is_mod, has_verified_email: data.has_verified_email, }; } /** * Fetches the authenticated user's preferences * * @returns User preferences including notification settings, content filters, and UI preferences * @throws {RedditError} Thrown if API request fails * * @example * ```typescript * const prefs = await authService.fetchUserPreferences(); * console.log(`NSFW content enabled: ${prefs.show_nsfw}`); * console.log(`Theme: ${prefs.theme}`); * ``` */ public async fetchUserPreferences() { const token = await this.getAccessToken(); const response = await fetch("https://oauth.reddit.com/api/v1/me/prefs", { headers: { Authorization: `Bearer ${token}`, "User-Agent": this.userAgent, }, }); if (!response.ok) { throw new RedditError( `Failed to fetch user preferences: ${response.statusText}`, "API_ERROR", ); } const prefs: any = await response.json(); return { enable_notifications: prefs.enable_notifications ?? true, show_nsfw: prefs.over_18 ?? false, default_comment_sort: prefs.default_comment_sort ?? "best", theme: prefs.theme ?? "dark", language: prefs.language ?? "en", }; } /** * Checks if the current access token is expired * * @returns True if token is expired or expiration time is unknown * @internal */ private isTokenExpired(): boolean { return !this.tokenExpiresAt || Date.now() >= this.tokenExpiresAt; } /** * Refreshes the access token using the refresh token * * @throws {RedditError} Thrown if token refresh fails * * @remarks * This method uses the OAuth2 refresh token grant type to obtain * a new access token. The refresh token itself never expires but * can be revoked by the user. * * @see {@link https://github.com/reddit-archive/reddit/wiki/OAuth2#refreshing-the-token} Reddit Token Refresh Documentation * @internal */ public async refreshAccessToken(): Promise<void> { const auth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString( "base64", ); const response = await fetch(this.tokenEndpoint, { method: "POST", headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/x-www-form-urlencoded", "User-Agent": this.userAgent, }, body: `grant_type=refresh_token&refresh_token=${this.config.refreshToken}`, }); if (!response.ok) { throw new RedditError(`Failed to refresh access token: ${response.statusText}`, "AUTH_ERROR"); } const data: any = await response.json(); this.accessToken = data.access_token; this.tokenExpiresAt = Date.now() + data.expires_in * 1000; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ozipi/brainloop-mcp-server-v2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server