Change prettier rules

This commit is contained in:
ErickSkrauch
2020-05-24 02:08:24 +03:00
parent 73f0c37a6a
commit f85b9d8d35
382 changed files with 24137 additions and 26046 deletions

View File

@@ -1,101 +1,88 @@
import request from 'app/services/request';
export interface UserResponse {
elyProfileLink: string;
email: string;
hasMojangUsernameCollision: boolean;
id: number;
isActive: boolean;
isOtpEnabled: boolean;
lang: string;
passwordChangedAt: number; // timestamp
registeredAt: number; // timestamp
shouldAcceptRules: boolean;
username: string;
uuid: string;
elyProfileLink: string;
email: string;
hasMojangUsernameCollision: boolean;
id: number;
isActive: boolean;
isOtpEnabled: boolean;
lang: string;
passwordChangedAt: number; // timestamp
registeredAt: number; // timestamp
shouldAcceptRules: boolean;
username: string;
uuid: string;
}
export function getInfo(id: number, token?: string): Promise<UserResponse> {
return request.get(
`/api/v1/accounts/${id}`,
{},
{
token,
},
);
return request.get(
`/api/v1/accounts/${id}`,
{},
{
token,
},
);
}
export function changePassword(
id: number,
{
password = '',
newPassword = '',
newRePassword = '',
logoutAll = true,
}: {
password?: string;
newPassword?: string;
newRePassword?: string;
logoutAll?: boolean;
},
id: number,
{
password = '',
newPassword = '',
newRePassword = '',
logoutAll = true,
}: {
password?: string;
newPassword?: string;
newRePassword?: string;
logoutAll?: boolean;
},
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/password`, {
password,
newPassword,
newRePassword,
logoutAll,
});
return request.post(`/api/v1/accounts/${id}/password`, {
password,
newPassword,
newRePassword,
logoutAll,
});
}
export function acceptRules(id: number): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/rules`);
return request.post(`/api/v1/accounts/${id}/rules`);
}
export function changeUsername(
id: number,
username: string | void,
password: string | void,
id: number,
username: string | void,
password: string | void,
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/username`, {
username,
password,
});
return request.post(`/api/v1/accounts/${id}/username`, {
username,
password,
});
}
export function changeLang(
id: number,
lang: string,
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/language`, {
lang,
});
export function changeLang(id: number, lang: string): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/language`, {
lang,
});
}
export function requestEmailChange(
id: number,
password: string,
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/email-verification`, {
password,
});
export function requestEmailChange(id: number, password: string): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/email-verification`, {
password,
});
}
export function setNewEmail(
id: number,
email: string,
key: string,
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/new-email-verification`, {
email,
key,
});
export function setNewEmail(id: number, email: string, key: string): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/new-email-verification`, {
email,
key,
});
}
export function confirmNewEmail(
id: number,
key: string,
): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/email`, {
key,
});
export function confirmNewEmail(id: number, key: string): Promise<{ success: boolean }> {
return request.post(`/api/v1/accounts/${id}/email`, {
key,
});
}

View File

@@ -5,75 +5,59 @@ import request from 'app/services/request';
import * as signup from 'app/services/api/signup';
describe('signup api', () => {
describe('#register', () => {
const params = {
email: 'email',
username: 'username',
password: 'password',
rePassword: 'rePassword',
rulesAgreement: false,
lang: 'lang',
captcha: 'captcha',
};
describe('#register', () => {
const params = {
email: 'email',
username: 'username',
password: 'password',
rePassword: 'rePassword',
rulesAgreement: false,
lang: 'lang',
captcha: 'captcha',
};
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should post to register api', () => {
signup.register(params);
expect(request.post, 'to have a call satisfying', ['/api/signup', params, {}]);
});
it('should disable any token', () => {
signup.register(params);
expect(request.post, 'to have a call satisfying', ['/api/signup', params, { token: null }]);
});
});
afterEach(() => {
(request.post as any).restore();
describe('#activate', () => {
const key = 'key';
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should post to confirmation api', () => {
signup.activate(key);
expect(request.post, 'to have a call satisfying', ['/api/signup/confirm', { key }, {}]);
});
it('should disable any token', () => {
signup.activate(key);
expect(request.post, 'to have a call satisfying', ['/api/signup/confirm', { key }, { token: null }]);
});
});
it('should post to register api', () => {
signup.register(params);
expect(request.post, 'to have a call satisfying', [
'/api/signup',
params,
{},
]);
});
it('should disable any token', () => {
signup.register(params);
expect(request.post, 'to have a call satisfying', [
'/api/signup',
params,
{ token: null },
]);
});
});
describe('#activate', () => {
const key = 'key';
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should post to confirmation api', () => {
signup.activate(key);
expect(request.post, 'to have a call satisfying', [
'/api/signup/confirm',
{ key },
{},
]);
});
it('should disable any token', () => {
signup.activate(key);
expect(request.post, 'to have a call satisfying', [
'/api/signup/confirm',
{ key },
{ token: null },
]);
});
});
});

View File

@@ -6,334 +6,261 @@ import * as authentication from 'app/services/api/authentication';
import * as accounts from 'app/services/api/accounts';
describe('authentication api', () => {
let server: SinonFakeServer;
beforeEach(() => {
server = sinon.fakeServer.create({
autoRespond: true,
});
});
afterEach(() => {
server.restore();
});
describe('#login', () => {
const params = {
login: 'foo',
password: 'secret',
rememberMe: false,
};
let server: SinonFakeServer;
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
(request.post as any).returns(Promise.resolve());
server = sinon.fakeServer.create({
autoRespond: true,
});
});
afterEach(() => {
(request.post as any).restore();
server.restore();
});
it('should post to login api', () => {
authentication.login(params);
describe('#login', () => {
const params = {
login: 'foo',
password: 'secret',
rememberMe: false,
};
expect(request.post, 'to have a call satisfying', [
'/api/authentication/login',
params,
{},
]);
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
(request.post as any).returns(Promise.resolve());
});
afterEach(() => {
(request.post as any).restore();
});
it('should post to login api', () => {
authentication.login(params);
expect(request.post, 'to have a call satisfying', ['/api/authentication/login', params, {}]);
});
it('should disable any token', () => {
authentication.login(params);
expect(request.post, 'to have a call satisfying', ['/api/authentication/login', params, { token: null }]);
});
});
it('should disable any token', () => {
authentication.login(params);
describe('#validateToken()', () => {
const validToken = 'foo';
const validRefreshToken = 'bar';
const user = { id: 1 };
const validateTokenArgs: [number, string, string] = [user.id, validToken, validRefreshToken];
expect(request.post, 'to have a call satisfying', [
'/api/authentication/login',
params,
{ token: null },
]);
});
});
beforeEach(() => {
sinon.stub(accounts, 'getInfo');
(accounts.getInfo as any).returns(Promise.resolve(user));
});
describe('#validateToken()', () => {
const validToken = 'foo';
const validRefreshToken = 'bar';
const user = { id: 1 };
const validateTokenArgs: [number, string, string] = [
user.id,
validToken,
validRefreshToken,
];
afterEach(() => {
(accounts.getInfo as any).restore();
});
beforeEach(() => {
sinon.stub(accounts, 'getInfo');
(accounts.getInfo as any).returns(Promise.resolve(user));
it('should request accounts.getInfo', () =>
expect(authentication.validateToken(...validateTokenArgs), 'to be fulfilled').then(() => {
expect(accounts.getInfo, 'to have a call satisfying', [user.id, validToken]);
}));
it('should resolve with both tokens and user object', () =>
expect(authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', {
token: validToken,
refreshToken: validRefreshToken,
user,
}));
it('rejects if token has a bad type', () =>
expect(authentication.validateToken(user.id, {} as any), 'to be rejected with', 'token must be a string'));
it('should allow empty refreshToken', () =>
expect(authentication.validateToken(user.id, 'foo', null), 'to be fulfilled'));
it('rejects if accounts.getInfo request is unexpectedly failed', () => {
const error = 'Something wrong';
(accounts.getInfo as any).returns(Promise.reject(error));
return expect(authentication.validateToken(...validateTokenArgs), 'to be rejected with', error);
});
describe('when token is expired', () => {
const expiredResponse = {
name: 'Unauthorized',
message: 'Token expired',
code: 0,
status: 401,
type: 'yii\\web\\UnauthorizedHttpException',
};
const newToken = 'baz';
beforeEach(() => {
sinon.stub(authentication, 'requestToken');
(accounts.getInfo as any).onCall(0).returns(Promise.reject(expiredResponse));
(authentication.requestToken as any).returns(Promise.resolve(newToken));
});
afterEach(() => {
(authentication.requestToken as any).restore();
});
it('resolves with new token and user object', async () => {
server.respondWith(
'POST',
'/api/authentication/refresh-token',
JSON.stringify({
access_token: newToken,
refresh_token: validRefreshToken,
success: true,
expires_in: 50000,
}),
);
await expect(authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', {
token: newToken,
refreshToken: validRefreshToken,
user,
});
expect(server.requests[0].requestBody, 'to equal', `refresh_token=${validRefreshToken}`);
});
it('rejects if token request failed', () => {
const error = { error: 'Unexpected error example' };
server.respondWith('POST', '/api/authentication/refresh-token', [500, [], JSON.stringify(error)]);
return expect(authentication.validateToken(...validateTokenArgs), 'to be rejected with', error);
});
});
describe('when token is incorrect', () => {
const expiredResponse = {
name: 'Unauthorized',
message: 'Incorrect token',
code: 0,
status: 401,
type: 'yii\\web\\UnauthorizedHttpException',
};
const newToken = 'baz';
beforeEach(() => {
(accounts.getInfo as any).onCall(0).returns(Promise.reject(expiredResponse));
});
it('resolves with new token and user object', async () => {
server.respondWith(
'POST',
'/api/authentication/refresh-token',
JSON.stringify({
access_token: newToken,
refresh_token: validRefreshToken,
success: true,
expires_in: 50000,
}),
);
await expect(authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', {
token: newToken,
refreshToken: validRefreshToken,
user,
});
expect(server.requests[0].requestBody, 'to equal', `refresh_token=${validRefreshToken}`);
});
it('rejects if token request failed', () => {
const error = { error: 'Unexpected error example' };
server.respondWith('POST', '/api/authentication/refresh-token', [500, [], JSON.stringify(error)]);
return expect(authentication.validateToken(...validateTokenArgs), 'to be rejected with', error);
});
});
});
afterEach(() => {
(accounts.getInfo as any).restore();
describe('#logout', () => {
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should request logout api', () => {
authentication.logout();
expect(request.post, 'to have a call satisfying', ['/api/authentication/logout', {}, {}]);
});
it('returns a promise', () => {
(request.post as any).returns(Promise.resolve());
return expect(authentication.logout(), 'to be fulfilled');
});
it('overrides token if provided', () => {
const token = 'foo';
authentication.logout(token);
expect(request.post, 'to have a call satisfying', ['/api/authentication/logout', {}, { token }]);
});
});
it('should request accounts.getInfo', () =>
expect(
authentication.validateToken(...validateTokenArgs),
'to be fulfilled',
).then(() => {
expect(accounts.getInfo, 'to have a call satisfying', [
user.id,
validToken,
]);
}));
describe('#requestToken', () => {
const refreshToken = 'refresh-token';
it('should resolve with both tokens and user object', () =>
expect(
authentication.validateToken(...validateTokenArgs),
'to be fulfilled with',
{
token: validToken,
refreshToken: validRefreshToken,
user,
},
));
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
it('rejects if token has a bad type', () =>
expect(
authentication.validateToken(user.id, {} as any),
'to be rejected with',
'token must be a string',
));
afterEach(() => {
(request.post as any).restore();
});
it('should allow empty refreshToken', () =>
expect(
authentication.validateToken(user.id, 'foo', null),
'to be fulfilled',
));
it('should request refresh-token api', () => {
(request.post as any).returns(Promise.resolve({}));
it('rejects if accounts.getInfo request is unexpectedly failed', () => {
const error = 'Something wrong';
(accounts.getInfo as any).returns(Promise.reject(error));
authentication.requestToken(refreshToken);
return expect(
authentication.validateToken(...validateTokenArgs),
'to be rejected with',
error,
);
expect(request.post, 'to have a call satisfying', [
'/api/authentication/refresh-token',
{
refresh_token: refreshToken, // eslint-disable-line
},
{},
]);
});
it('should disable bearer auth for request', () => {
(request.post as any).returns(Promise.resolve({}));
authentication.requestToken(refreshToken);
expect(request.post, 'to have a call satisfying', [
'/api/authentication/refresh-token',
{
refresh_token: refreshToken, // eslint-disable-line
},
{ token: null },
]);
});
it('should resolve with token', () => {
const token = 'token';
(request.post as any).returns(
Promise.resolve({
access_token: token, // eslint-disable-line
}),
);
return expect(authentication.requestToken(refreshToken), 'to be fulfilled with', token);
});
});
describe('when token is expired', () => {
const expiredResponse = {
name: 'Unauthorized',
message: 'Token expired',
code: 0,
status: 401,
type: 'yii\\web\\UnauthorizedHttpException',
};
const newToken = 'baz';
beforeEach(() => {
sinon.stub(authentication, 'requestToken');
(accounts.getInfo as any)
.onCall(0)
.returns(Promise.reject(expiredResponse));
(authentication.requestToken as any).returns(Promise.resolve(newToken));
});
afterEach(() => {
(authentication.requestToken as any).restore();
});
it('resolves with new token and user object', async () => {
server.respondWith(
'POST',
'/api/authentication/refresh-token',
JSON.stringify({
access_token: newToken,
refresh_token: validRefreshToken,
success: true,
expires_in: 50000,
}),
);
await expect(
authentication.validateToken(...validateTokenArgs),
'to be fulfilled with',
{ token: newToken, refreshToken: validRefreshToken, user },
);
expect(
server.requests[0].requestBody,
'to equal',
`refresh_token=${validRefreshToken}`,
);
});
it('rejects if token request failed', () => {
const error = { error: 'Unexpected error example' };
server.respondWith('POST', '/api/authentication/refresh-token', [
500,
[],
JSON.stringify(error),
]);
return expect(
authentication.validateToken(...validateTokenArgs),
'to be rejected with',
error,
);
});
});
describe('when token is incorrect', () => {
const expiredResponse = {
name: 'Unauthorized',
message: 'Incorrect token',
code: 0,
status: 401,
type: 'yii\\web\\UnauthorizedHttpException',
};
const newToken = 'baz';
beforeEach(() => {
(accounts.getInfo as any)
.onCall(0)
.returns(Promise.reject(expiredResponse));
});
it('resolves with new token and user object', async () => {
server.respondWith(
'POST',
'/api/authentication/refresh-token',
JSON.stringify({
access_token: newToken,
refresh_token: validRefreshToken,
success: true,
expires_in: 50000,
}),
);
await expect(
authentication.validateToken(...validateTokenArgs),
'to be fulfilled with',
{ token: newToken, refreshToken: validRefreshToken, user },
);
expect(
server.requests[0].requestBody,
'to equal',
`refresh_token=${validRefreshToken}`,
);
});
it('rejects if token request failed', () => {
const error = { error: 'Unexpected error example' };
server.respondWith('POST', '/api/authentication/refresh-token', [
500,
[],
JSON.stringify(error),
]);
return expect(
authentication.validateToken(...validateTokenArgs),
'to be rejected with',
error,
);
});
});
});
describe('#logout', () => {
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should request logout api', () => {
authentication.logout();
expect(request.post, 'to have a call satisfying', [
'/api/authentication/logout',
{},
{},
]);
});
it('returns a promise', () => {
(request.post as any).returns(Promise.resolve());
return expect(authentication.logout(), 'to be fulfilled');
});
it('overrides token if provided', () => {
const token = 'foo';
authentication.logout(token);
expect(request.post, 'to have a call satisfying', [
'/api/authentication/logout',
{},
{ token },
]);
});
});
describe('#requestToken', () => {
const refreshToken = 'refresh-token';
beforeEach(() => {
sinon.stub(request, 'post').named('request.post');
});
afterEach(() => {
(request.post as any).restore();
});
it('should request refresh-token api', () => {
(request.post as any).returns(Promise.resolve({}));
authentication.requestToken(refreshToken);
expect(request.post, 'to have a call satisfying', [
'/api/authentication/refresh-token',
{
refresh_token: refreshToken, // eslint-disable-line
},
{},
]);
});
it('should disable bearer auth for request', () => {
(request.post as any).returns(Promise.resolve({}));
authentication.requestToken(refreshToken);
expect(request.post, 'to have a call satisfying', [
'/api/authentication/refresh-token',
{
refresh_token: refreshToken, // eslint-disable-line
},
{ token: null },
]);
});
it('should resolve with token', () => {
const token = 'token';
(request.post as any).returns(
Promise.resolve({
access_token: token, // eslint-disable-line
}),
);
return expect(
authentication.requestToken(refreshToken),
'to be fulfilled with',
token,
);
});
});
});

View File

@@ -4,33 +4,33 @@ 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;
access_token: string;
refresh_token?: string;
expires_in: number; // count seconds before expire
success: true;
}
export function login({
login,
password,
totp,
rememberMe = false,
login,
password,
totp,
rememberMe = false,
}: {
login: string;
password?: string;
totp?: string;
rememberMe: boolean;
login: string;
password?: string;
totp?: string;
rememberMe: boolean;
}): Promise<OAuthResponse> {
return request.post(
'/api/authentication/login',
{
login,
password,
totp,
rememberMe,
},
{ token: null },
);
return request.post(
'/api/authentication/login',
{
login,
password,
totp,
rememberMe,
},
{ token: null },
);
}
/**
@@ -40,53 +40,49 @@ export function login({
* @returns {Promise}
*/
export function logout(token?: string): Promise<{ success: boolean }> {
return request.post(
'/api/authentication/logout',
{},
{
token,
},
);
return request.post(
'/api/authentication/logout',
{},
{
token,
},
);
}
export function forgotPassword(
login: string,
captcha: string,
login: string,
captcha: string,
): Promise<{
success: boolean;
data: {
canRepeatIn: number;
emailMask: string | void;
repeatFrequency: number;
};
errors: {
[key: string]: string;
};
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 },
);
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 },
);
export function recoverPassword(key: string, newPassword: string, newRePassword: string): Promise<OAuthResponse> {
return request.post(
'/api/authentication/recover-password',
{
key,
newPassword,
newRePassword,
},
{ token: null },
);
}
/**
@@ -102,59 +98,52 @@ export function recoverPassword(
*
*/
export async function validateToken(
id: number,
token: string,
refreshToken: string | void | null,
id: number,
token: string,
refreshToken: string | void | null,
): Promise<{
token: string;
refreshToken: string | null;
user: UserResponse;
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);
if (typeof token !== 'string') {
throw new Error('token must be a string');
}
logger.error('Unexpected error during token validation', { resp });
}
refreshToken = refreshToken || null;
return Promise.reject(resp);
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);
}
/**
@@ -165,27 +154,27 @@ function handleTokenError(
* @returns {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,
},
{
token: null,
},
);
try {
const response: OAuthResponse = await request.post(
'/api/authentication/refresh-token',
{
refresh_token: refreshToken,
},
{
token: null,
},
);
return response.access_token;
} catch (resp) {
const errors = resp.errors || {};
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,
});
if (errors.refresh_token !== 'error.refresh_token_not_exist') {
logger.error('Failed refreshing token: unknown error', {
resp,
});
}
throw resp;
}
throw resp;
}
}

View File

@@ -1,12 +1,12 @@
import request from 'app/services/request';
export default {
send({ subject = '', email = '', message = '', category = '' }) {
return request.post('/api/feedback', {
subject,
email,
message,
category,
});
},
send({ subject = '', email = '', message = '', category = '' }) {
return request.post('/api/feedback', {
subject,
email,
message,
category,
});
},
};

View File

@@ -1,35 +1,27 @@
import request, { Resp } from 'app/services/request';
export function getSecret(
id: number,
id: number,
): Promise<
Resp<{
qr: string;
secret: string;
uri: string;
}>
Resp<{
qr: string;
secret: string;
uri: string;
}>
> {
return request.get(`/api/v1/accounts/${id}/two-factor-auth`);
return request.get(`/api/v1/accounts/${id}/two-factor-auth`);
}
export function enable(
id: number,
totp: string,
password?: string,
): Promise<Resp<any>> {
return request.post(`/api/v1/accounts/${id}/two-factor-auth`, {
totp,
password,
});
export function enable(id: number, totp: string, password?: string): Promise<Resp<any>> {
return request.post(`/api/v1/accounts/${id}/two-factor-auth`, {
totp,
password,
});
}
export function disable(
id: number,
totp: string,
password?: string,
): Promise<Resp<any>> {
return request.delete(`/api/v1/accounts/${id}/two-factor-auth`, {
totp,
password,
});
export function disable(id: number, totp: string, password?: string): Promise<Resp<any>> {
return request.delete(`/api/v1/accounts/${id}/two-factor-auth`, {
totp,
password,
});
}

View File

@@ -1,164 +1,147 @@
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 type Scope = 'minecraft_server_session' | 'offline_access' | 'account_info' | 'account_email';
export interface Client {
id: string;
name: string;
description: string;
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;
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;
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;
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
session: {
scopes: Scope[];
};
client: Client;
oAuth: {}; // TODO: improve typing
}
interface FormPayloads {
name?: string;
description?: string;
websiteUrl?: string;
redirectUri?: string;
minecraftServerIp?: string;
name?: string;
description?: string;
websiteUrl?: string;
redirectUri?: string;
minecraftServerIp?: string;
}
const api = {
validate(oauthData: OauthData) {
return request
.get<OAuthValidateResponse>(
'/api/oauth2/v1/validate',
getOAuthRequest(oauthData),
)
.catch(handleOauthParamsValidation);
},
validate(oauthData: OauthData) {
return request
.get<OAuthValidateResponse>('/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<{
complete(
oauthData: OauthData,
params: { accept?: boolean } = {},
): Promise<{
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,
};
}
}> {
const query = request.buildQuery(getOAuthRequest(oauthData));
if (resp.status === 401 && resp.name === 'Unauthorized') {
const error: { [key: string]: any } = new Error('Unauthorized');
error.unauthorized = true;
throw error;
}
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.statusCode === 401 && resp.error === 'accept_required') {
const error: { [key: string]: any } = new Error(
'Permissions accept required',
);
error.acceptRequired = true;
throw error;
}
if (resp.status === 401 && resp.name === 'Unauthorized') {
const error: { [key: string]: any } = new Error('Unauthorized');
error.unauthorized = true;
throw error;
}
return handleOauthParamsValidation(resp);
});
},
if (resp.statusCode === 401 && resp.error === 'accept_required') {
const error: { [key: string]: any } = new Error('Permissions accept required');
error.acceptRequired = true;
throw error;
}
create(type: string, formParams: FormPayloads) {
return request.post<{ success: boolean; data: OauthAppResponse }>(
`/api/v1/oauth2/${type}`,
formParams,
);
},
return handleOauthParamsValidation(resp);
});
},
update(clientId: string, formParams: FormPayloads) {
return request.put<{ success: boolean; data: OauthAppResponse }>(
`/api/v1/oauth2/${clientId}`,
formParams,
);
},
create(type: string, formParams: FormPayloads) {
return request.post<{ success: boolean; data: OauthAppResponse }>(`/api/v1/oauth2/${type}`, formParams);
},
getApp(clientId: string) {
return request.get<OauthAppResponse>(`/api/v1/oauth2/${clientId}`);
},
update(clientId: string, formParams: FormPayloads) {
return request.put<{ success: boolean; data: OauthAppResponse }>(`/api/v1/oauth2/${clientId}`, formParams);
},
getAppsByUser(userId: number): Promise<OauthAppResponse[]> {
return request.get(`/api/v1/accounts/${userId}/oauth2/clients`);
},
getApp(clientId: string) {
return request.get<OauthAppResponse>(`/api/v1/oauth2/${clientId}`);
},
reset(clientId: string, regenerateSecret: boolean = false) {
return request.post<{ success: boolean; data: OauthAppResponse }>(
`/api/v1/oauth2/${clientId}/reset${
regenerateSecret ? '?regenerateSecret' : ''
}`,
);
},
getAppsByUser(userId: number): Promise<OauthAppResponse[]> {
return request.get(`/api/v1/accounts/${userId}/oauth2/clients`);
},
delete(clientId: string) {
return request.delete<{ success: boolean }>(`/api/v1/oauth2/${clientId}`);
},
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;
(window as any).oauthApi = api;
}
export default api;
@@ -175,48 +158,39 @@ export default api;
* @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,
};
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;
} = {},
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;
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.';
}
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);
return userMessage ? Promise.reject({ ...resp, userMessage }) : Promise.reject(resp);
}

View File

@@ -4,47 +4,43 @@ import request from 'app/services/request';
import options from './options';
describe('services/api/options', () => {
const expectedResp = {
foo: 'bar',
};
const expectedResp = {
foo: 'bar',
};
beforeEach(() => {
sinon
.stub(request, 'get')
.named('request.get')
.returns(
Promise.resolve({
originalResponse: new Response(),
...expectedResp,
}),
);
});
beforeEach(() => {
sinon
.stub(request, 'get')
.named('request.get')
.returns(
Promise.resolve({
originalResponse: new Response(),
...expectedResp,
}),
);
});
afterEach(() => {
(request.get as any).restore();
});
afterEach(() => {
(request.get as any).restore();
});
it('should request options without token', () =>
options.get().then((resp) => {
expect(resp, 'to satisfy', {
...expectedResp,
originalResponse: expect.it('to be a', Response),
});
expect(request.get, 'to have a call satisfying', [
'/api/options',
{},
{ token: null },
]);
}));
it('should request options without token', () =>
options.get().then((resp) => {
expect(resp, 'to satisfy', {
...expectedResp,
originalResponse: expect.it('to be a', Response),
});
expect(request.get, 'to have a call satisfying', ['/api/options', {}, { token: null }]);
}));
it('should cache options', () =>
// NOTE: this is bad practice, but we are relying on the state from
// the previous test
options.get().then((resp) => {
expect(resp, 'to satisfy', {
...expectedResp,
originalResponse: expect.it('to be a', Response),
});
expect(request.get, 'was not called');
}));
it('should cache options', () =>
// NOTE: this is bad practice, but we are relying on the state from
// the previous test
options.get().then((resp) => {
expect(resp, 'to satisfy', {
...expectedResp,
originalResponse: expect.it('to be a', Response),
});
expect(request.get, 'was not called');
}));
});

View File

@@ -5,19 +5,15 @@ type Options = { reCaptchaPublicKey: string };
let options: Resp<Options>;
export default {
async get(): Promise<Resp<Options>> {
if (options) {
return Promise.resolve(options);
}
async get(): Promise<Resp<Options>> {
if (options) {
return Promise.resolve(options);
}
const resp = await request.get<Options>(
'/api/options',
{},
{ token: null },
);
const resp = await request.get<Options>('/api/options', {}, { token: null });
options = resp;
options = resp;
return resp;
},
return resp;
},
};

View File

@@ -3,35 +3,35 @@ import request, { Resp } from 'app/services/request';
import { OAuthResponse } from './authentication';
interface RegisterParams {
email?: string;
username?: string;
password?: string;
rePassword?: string;
rulesAgreement?: boolean;
lang?: string;
captcha?: string;
email?: string;
username?: string;
password?: string;
rePassword?: string;
rulesAgreement?: boolean;
lang?: string;
captcha?: string;
}
export function register({
email = '',
username = '',
password = '',
rePassword = '',
rulesAgreement = false,
lang = '',
captcha = '',
email = '',
username = '',
password = '',
rePassword = '',
rulesAgreement = false,
lang = '',
captcha = '',
}: RegisterParams): Promise<Resp<void>> {
return request.post(
'/api/signup',
{ email, username, password, rePassword, rulesAgreement, lang, captcha },
{ token: null },
);
return request.post(
'/api/signup',
{ email, username, password, rePassword, rulesAgreement, lang, captcha },
{ token: null },
);
}
export function activate(key: string = ''): Promise<Resp<OAuthResponse>> {
return request.post('/api/signup/confirm', { key }, { token: null });
return request.post('/api/signup/confirm', { key }, { token: null });
}
export function resendActivation(email: string = '', captcha: string = '') {
return request.post('/api/signup/repeat-message', { email, captcha });
return request.post('/api/signup/repeat-message', { email, captcha });
}