2017-08-23 00:09:08 +05:30
|
|
|
// @flow
|
2019-01-25 22:59:32 +05:30
|
|
|
import type { UserResponse } from 'services/api/accounts';
|
2017-12-26 01:33:21 +05:30
|
|
|
import logger from 'services/logger';
|
|
|
|
import request, { InternalServerError } from 'services/request';
|
2019-01-25 22:59:32 +05:30
|
|
|
import { getInfo as getInfoEndpoint } from 'services/api/accounts';
|
2016-06-04 19:16:39 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
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: bool,
|
|
|
|
}): Promise<OAuthResponse> {
|
|
|
|
return request.post('/api/authentication/login', {
|
2017-08-23 00:09:08 +05:30
|
|
|
login,
|
|
|
|
password,
|
|
|
|
totp,
|
2019-01-28 00:42:58 +05:30
|
|
|
rememberMe,
|
|
|
|
}, { token: null });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} token - an optional token to overwrite headers
|
|
|
|
* in middleware and disable token auto-refresh
|
|
|
|
*
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
export function logout(token?: string): Promise<{success: bool}> {
|
|
|
|
return request.post('/api/authentication/logout', {}, {
|
|
|
|
token,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function forgotPassword(login: string, captcha: string): Promise<{
|
|
|
|
success: bool,
|
|
|
|
data: {
|
|
|
|
canRepeatIn: number,
|
|
|
|
emailMask: ?string,
|
|
|
|
repeatFrequency: number,
|
2016-06-04 20:28:29 +05:30
|
|
|
},
|
2019-01-28 00:42:58 +05:30
|
|
|
errors: {
|
|
|
|
[key: string]: string,
|
2016-06-28 15:43:27 +05:30
|
|
|
},
|
2019-01-28 00:42:58 +05:30
|
|
|
}> {
|
|
|
|
return request.post('/api/authentication/forgot-password', {
|
2017-08-23 00:09:08 +05:30
|
|
|
login,
|
2019-01-28 00:42:58 +05:30
|
|
|
captcha,
|
|
|
|
}, { token: null });
|
|
|
|
}
|
2016-07-28 10:33:30 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
export function recoverPassword(key: string, newPassword: string, newRePassword: string): Promise<OAuthResponse> {
|
|
|
|
return request.post('/api/authentication/recover-password', {
|
2017-08-23 00:09:08 +05:30
|
|
|
key,
|
|
|
|
newPassword,
|
2019-01-28 00:42:58 +05:30
|
|
|
newRePassword,
|
|
|
|
}, { token: null });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves if token is valid
|
|
|
|
*
|
|
|
|
* @param {number} id
|
|
|
|
* @param {string} token
|
|
|
|
* @param {string} refreshToken
|
|
|
|
*
|
|
|
|
* @return {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): Promise<{
|
|
|
|
token: string,
|
|
|
|
refreshToken: ?string,
|
|
|
|
user: UserResponse,
|
|
|
|
}> {
|
|
|
|
if (typeof token !== 'string') {
|
|
|
|
throw new Error('token must be a string');
|
|
|
|
}
|
2016-07-28 10:33:30 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
let user: UserResponse;
|
|
|
|
try {
|
|
|
|
user = await getInfoEndpoint(id, token);
|
|
|
|
} catch (resp) {
|
2019-02-05 02:44:58 +05:30
|
|
|
token = await handleTokenError(resp, refreshToken);
|
2019-01-28 00:42:58 +05:30
|
|
|
user = await getInfoEndpoint(id, token); // TODO: replace with recursive call
|
|
|
|
}
|
2019-01-25 22:59:32 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
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): Promise<string> {
|
|
|
|
if (resp instanceof InternalServerError) {
|
|
|
|
// delegate error recovering to the bsod middleware
|
|
|
|
return new Promise(() => {});
|
|
|
|
}
|
2019-01-25 22:59:32 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
if (refreshToken) {
|
|
|
|
if (recoverableErrors.includes(resp.message)) {
|
|
|
|
return requestToken(refreshToken);
|
2019-01-25 22:59:32 +05:30
|
|
|
}
|
2016-11-05 15:41:41 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
logger.error('Unexpected error during token validation', { resp });
|
|
|
|
}
|
2017-12-26 01:33:21 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
return Promise.reject(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request new access token using a refreshToken
|
|
|
|
*
|
|
|
|
* @param {string} refreshToken
|
|
|
|
*
|
|
|
|
* @return {Promise} - resolves to token
|
|
|
|
*/
|
|
|
|
export async function requestToken(refreshToken: string): Promise<string> {
|
|
|
|
try {
|
|
|
|
const response: OAuthResponse = await request.post('/api/authentication/refresh-token', {
|
|
|
|
refresh_token: refreshToken, // eslint-disable-line camelcase
|
|
|
|
}, {
|
|
|
|
token: null,
|
|
|
|
});
|
2017-12-26 01:33:21 +05:30
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
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,
|
2017-08-23 02:30:10 +05:30
|
|
|
});
|
2017-12-31 00:34:31 +05:30
|
|
|
}
|
|
|
|
|
2019-01-28 00:42:58 +05:30
|
|
|
throw resp;
|
2016-06-04 19:16:39 +05:30
|
|
|
}
|
2019-01-28 00:42:58 +05:30
|
|
|
}
|