mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-30 10:42:34 +05:30
193 lines
4.0 KiB
TypeScript
193 lines
4.0 KiB
TypeScript
import { UserResponse } from 'app/services/api/accounts';
|
|
import logger from 'app/services/logger';
|
|
import request, { InternalServerError } from 'app/services/request';
|
|
import { getInfo as getInfoEndpoint } from 'app/services/api/accounts';
|
|
|
|
export interface OAuthResponse {
|
|
access_token: string;
|
|
refresh_token?: string;
|
|
expires_in: number; // count seconds before expire
|
|
success: true;
|
|
}
|
|
|
|
export function login({
|
|
login,
|
|
password,
|
|
totp,
|
|
rememberMe = false,
|
|
}: {
|
|
login: string;
|
|
password?: string;
|
|
totp?: string;
|
|
rememberMe: boolean;
|
|
}): Promise<OAuthResponse> {
|
|
return request.post(
|
|
'/api/authentication/login',
|
|
{
|
|
login,
|
|
password,
|
|
totp,
|
|
rememberMe,
|
|
},
|
|
{ token: null },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {string} token - an optional token to overwrite headers
|
|
* in middleware and disable token auto-refresh
|
|
*
|
|
* @returns {Promise}
|
|
*/
|
|
export function logout(token?: string): Promise<{ success: boolean }> {
|
|
return request.post(
|
|
'/api/authentication/logout',
|
|
{},
|
|
{
|
|
token,
|
|
},
|
|
);
|
|
}
|
|
|
|
export function forgotPassword(
|
|
login: string,
|
|
captcha: string,
|
|
): Promise<{
|
|
success: boolean;
|
|
data: {
|
|
canRepeatIn: number;
|
|
emailMask: string | void;
|
|
repeatFrequency: number;
|
|
};
|
|
errors: {
|
|
[key: string]: string;
|
|
};
|
|
}> {
|
|
return request.post(
|
|
'/api/authentication/forgot-password',
|
|
{
|
|
login,
|
|
captcha,
|
|
},
|
|
{ token: null },
|
|
);
|
|
}
|
|
|
|
export function recoverPassword(
|
|
key: string,
|
|
newPassword: string,
|
|
newRePassword: string,
|
|
): Promise<OAuthResponse> {
|
|
return request.post(
|
|
'/api/authentication/recover-password',
|
|
{
|
|
key,
|
|
newPassword,
|
|
newRePassword,
|
|
},
|
|
{ token: null },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolves if token is valid
|
|
*
|
|
* @param {number} id
|
|
* @param {string} token
|
|
* @param {string} refreshToken
|
|
*
|
|
* @returns {Promise} - resolves with options.token or with a new token
|
|
* if it was refreshed. As a side effect the response
|
|
* will have a `user` field with current user data
|
|
*
|
|
*/
|
|
export async function validateToken(
|
|
id: number,
|
|
token: string,
|
|
refreshToken: string | void | null,
|
|
): Promise<{
|
|
token: string;
|
|
refreshToken: string | null;
|
|
user: UserResponse;
|
|
}> {
|
|
if (typeof token !== 'string') {
|
|
throw new Error('token must be a string');
|
|
}
|
|
|
|
refreshToken = refreshToken || null;
|
|
|
|
let user: UserResponse;
|
|
try {
|
|
user = await getInfoEndpoint(id, token);
|
|
} catch (resp) {
|
|
token = await handleTokenError(resp, refreshToken);
|
|
user = await getInfoEndpoint(id, token); // TODO: replace with recursive call
|
|
}
|
|
|
|
return {
|
|
token,
|
|
refreshToken,
|
|
user,
|
|
};
|
|
}
|
|
|
|
const recoverableErrors = [
|
|
'Token expired',
|
|
'Incorrect token',
|
|
'You are requesting with an invalid credential.',
|
|
];
|
|
|
|
function handleTokenError(
|
|
resp: Error | { message: string },
|
|
refreshToken: string | null,
|
|
): Promise<string> {
|
|
if (resp instanceof InternalServerError) {
|
|
// delegate error recovering to the bsod middleware
|
|
return new Promise(() => {});
|
|
}
|
|
|
|
if (refreshToken) {
|
|
if (recoverableErrors.includes(resp.message)) {
|
|
return requestToken(refreshToken);
|
|
}
|
|
|
|
logger.error('Unexpected error during token validation', { resp });
|
|
}
|
|
|
|
return Promise.reject(resp);
|
|
}
|
|
|
|
/**
|
|
* Request new access token using a refreshToken
|
|
*
|
|
* @param {string} refreshToken
|
|
*
|
|
* @returns {Promise} - resolves to token
|
|
*/
|
|
export async function requestToken(refreshToken: string): Promise<string> {
|
|
try {
|
|
const response: OAuthResponse = await request.post(
|
|
'/api/authentication/refresh-token',
|
|
{
|
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
|
refresh_token: refreshToken,
|
|
},
|
|
{
|
|
token: null,
|
|
},
|
|
);
|
|
|
|
return response.access_token;
|
|
} catch (resp) {
|
|
const errors = resp.errors || {};
|
|
|
|
if (errors.refresh_token !== 'error.refresh_token_not_exist') {
|
|
logger.error('Failed refreshing token: unknown error', {
|
|
resp,
|
|
});
|
|
}
|
|
|
|
throw resp;
|
|
}
|
|
}
|