import { ApplicationType } from 'app/components/dev/apps'; import request from 'app/services/request'; export type Scope = 'minecraft_server_session' | 'offline_access' | 'account_info' | 'account_email'; export interface Client { id: string; name: string; description: string; } export interface OauthAppResponse { clientId: string; clientSecret: string; type: ApplicationType; name: string; websiteUrl: string; createdAt: number; // fields for 'application' type countUsers?: number; description?: string; redirectUri?: string; // fields for 'minecraft-server' type minecraftServerIp?: string; } interface OauthRequestData { client_id: string; redirect_uri: string; response_type: string; description?: string; scope: string; prompt: string; login_hint?: string; state?: string; } export interface OauthData { clientId: string; redirectUrl: string; responseType: string; description?: string; scope: string; // TODO: why prompt is not nullable? prompt: string; // comma separated list of 'none' | 'consent' | 'select_account'; loginHint?: string; state?: string; } export interface OAuthValidateResponse { session: { scopes: Scope[]; }; client: Client; oAuth: {}; // TODO: improve typing } interface FormPayloads { name?: string; description?: string; websiteUrl?: string; redirectUri?: string; minecraftServerIp?: string; } const api = { validate(oauthData: OauthData) { return request .get('/api/oauth2/v1/validate', getOAuthRequest(oauthData)) .catch(handleOauthParamsValidation); }, complete( oauthData: OauthData, params: { accept?: boolean } = {}, ): Promise<{ success: boolean; redirectUri: string; }> { const query = request.buildQuery(getOAuthRequest(oauthData)); return request .post<{ success: boolean; redirectUri: string; }>( `/api/oauth2/v1/complete?${query}`, typeof params.accept === 'undefined' ? {} : { accept: params.accept }, ) .catch((resp = {}) => { if (resp.statusCode === 401 && resp.error === 'access_denied') { // user declined permissions return { success: false, redirectUri: resp.redirectUri, originalResponse: resp.originalResponse, }; } if (resp.status === 401 && resp.name === 'Unauthorized') { const error: { [key: string]: any } = new Error('Unauthorized'); error.unauthorized = true; throw error; } if (resp.statusCode === 401 && resp.error === 'accept_required') { const error: { [key: string]: any } = new Error('Permissions accept required'); error.acceptRequired = true; throw error; } return handleOauthParamsValidation(resp); }); }, create(type: string, formParams: FormPayloads) { return request.post<{ success: boolean; data: OauthAppResponse }>(`/api/v1/oauth2/${type}`, formParams); }, update(clientId: string, formParams: FormPayloads) { return request.put<{ success: boolean; data: OauthAppResponse }>(`/api/v1/oauth2/${clientId}`, formParams); }, getApp(clientId: string) { return request.get(`/api/v1/oauth2/${clientId}`); }, getAppsByUser(userId: number): Promise { return request.get(`/api/v1/accounts/${userId}/oauth2/clients`); }, reset(clientId: string, regenerateSecret: boolean = false) { return request.post<{ success: boolean; data: OauthAppResponse }>( `/api/v1/oauth2/${clientId}/reset${regenerateSecret ? '?regenerateSecret' : ''}`, ); }, delete(clientId: string) { return request.delete<{ success: boolean }>(`/api/v1/oauth2/${clientId}`); }, }; if ('Cypress' in window) { (window as any).oauthApi = api; } export default api; /** * @param {object} oauthData * @param {string} oauthData.clientId * @param {string} oauthData.redirectUrl * @param {string} oauthData.responseType * @param {string} oauthData.description * @param {string} oauthData.scope * @param {string} oauthData.state * * @returns {object} */ function getOAuthRequest(oauthData: OauthData): OauthRequestData { return { client_id: oauthData.clientId, redirect_uri: oauthData.redirectUrl, response_type: oauthData.responseType, description: oauthData.description, scope: oauthData.scope, prompt: oauthData.prompt, login_hint: oauthData.loginHint, state: oauthData.state, }; } function handleOauthParamsValidation( resp: | { [key: string]: any } | { statusCode: number; success: false; error: 'invalid_request' | 'unsupported_response_type' | 'invalid_scope' | 'invalid_client'; parameter: string; } = {}, ) { let userMessage: string | null = null; if (resp.statusCode === 400 && resp.error === 'invalid_request') { userMessage = `Invalid request (${resp.parameter} required).`; } else if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') { userMessage = `Invalid response type '${resp.parameter}'.`; } else if (resp.statusCode === 400 && resp.error === 'invalid_scope') { userMessage = `Invalid scope '${resp.parameter}'.`; } else if (resp.statusCode === 401 && resp.error === 'invalid_client') { userMessage = 'Can not find application you are trying to authorize.'; } return userMessage ? Promise.reject({ ...resp, userMessage }) : Promise.reject(resp); }