diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index b1f151031f..d2dcfe8a6a 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -1696,6 +1696,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void { router.get('express.pingExpress', [typedPromiseWrapper(handlePingExpress)]); // auth + router.post('express.v1.login', [prepareBitGo(config), typedPromiseWrapper(handleLogin)]); router.post('express.login', [prepareBitGo(config), typedPromiseWrapper(handleLogin)]); router.post('express.v1.decrypt', [prepareBitGo(config), typedPromiseWrapper(handleDecrypt)]); diff --git a/modules/express/src/typedRoutes/api/index.ts b/modules/express/src/typedRoutes/api/index.ts index 25129de955..864b4244ac 100644 --- a/modules/express/src/typedRoutes/api/index.ts +++ b/modules/express/src/typedRoutes/api/index.ts @@ -4,7 +4,8 @@ import * as express from 'express'; import { GetPing } from './common/ping'; import { GetPingExpress } from './common/pingExpress'; -import { PostLogin } from './common/login'; +import { PostV1Login } from './v1/login'; +import { PostV2Login } from './v2/login'; import { PostV1Decrypt } from './v1/decrypt'; import { PostV2Decrypt } from './v2/decrypt'; import { PostV1Encrypt } from './v1/encrypt'; @@ -77,8 +78,11 @@ export const ExpressPingExpressApiSpec = apiSpec({ }); export const ExpressLoginApiSpec = apiSpec({ + 'express.v1.login': { + post: PostV1Login, + }, 'express.login': { - post: PostLogin, + post: PostV2Login, }, }); diff --git a/modules/express/src/typedRoutes/api/common/login.ts b/modules/express/src/typedRoutes/api/v1/login.ts similarity index 91% rename from modules/express/src/typedRoutes/api/common/login.ts rename to modules/express/src/typedRoutes/api/v1/login.ts index dcffab2523..d60b7f6a08 100644 --- a/modules/express/src/typedRoutes/api/common/login.ts +++ b/modules/express/src/typedRoutes/api/v1/login.ts @@ -39,13 +39,16 @@ export const LoginRequest = { }; /** - * Login + * Login (v1) * - * @operationId express.login - * @tag express + * Authenticate a user and retrieve their session details. + * + * @operationId express.v1.login + * @tag Express + * @private */ -export const PostLogin = httpRoute({ - path: '/api/v[12]/user/login', +export const PostV1Login = httpRoute({ + path: '/api/v1/user/login', method: 'POST', request: httpRequest({ body: LoginRequest, diff --git a/modules/express/src/typedRoutes/api/v2/login.ts b/modules/express/src/typedRoutes/api/v2/login.ts new file mode 100644 index 0000000000..ee8c7dd686 --- /dev/null +++ b/modules/express/src/typedRoutes/api/v2/login.ts @@ -0,0 +1,37 @@ +import * as t from 'io-ts'; +import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http'; +import { BitgoExpressError } from '../../schemas/error'; +import { LoginRequest } from '../v1/login'; + +/** + * Login + * + * Authenticate a user and retrieve their session details. + * + * @operationId express.login + * @tag Express + * @public + */ +export const PostV2Login = httpRoute({ + path: '/api/v2/user/login', + method: 'POST', + request: httpRequest({ + body: LoginRequest, + }), + response: { + 200: t.type({ + email: t.string, + password: t.string, + forceSMS: t.boolean, + otp: optional(t.string), + trust: optional(t.number), + extensible: optional(t.boolean), + extensionAddress: optional(t.string), + forceV1Auth: optional(t.boolean), + forReset2FA: optional(t.boolean), + initialHash: optional(t.string), + fingerprintHash: optional(t.string), + }), + 404: BitgoExpressError, + }, +}); diff --git a/modules/express/test/unit/typedRoutes/decode.ts b/modules/express/test/unit/typedRoutes/decode.ts index b7badc8c58..5e6b685ca4 100644 --- a/modules/express/test/unit/typedRoutes/decode.ts +++ b/modules/express/test/unit/typedRoutes/decode.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as t from 'io-ts'; import { DecryptRequestBody } from '../../../src/typedRoutes/api/v1/decrypt'; import { EncryptRequestBody } from '../../../src/typedRoutes/api/v1/encrypt'; -import { LoginRequest } from '../../../src/typedRoutes/api/common/login'; +import { LoginRequest } from '../../../src/typedRoutes/api/v1/login'; import { VerifyAddressBody } from '../../../src/typedRoutes/api/common/verifyAddress'; import { VerifyAddressV2Body, VerifyAddressV2Params } from '../../../src/typedRoutes/api/v2/verifyCoinAddress'; import { SimpleCreateRequestBody } from '../../../src/typedRoutes/api/v1/simpleCreate'; diff --git a/modules/express/test/unit/typedRoutes/userLogin.ts b/modules/express/test/unit/typedRoutes/userLogin.ts index beb9023790..6fe2177f61 100644 --- a/modules/express/test/unit/typedRoutes/userLogin.ts +++ b/modules/express/test/unit/typedRoutes/userLogin.ts @@ -1,6 +1,7 @@ import * as assert from 'assert'; import * as t from 'io-ts'; -import { LoginRequest, PostLogin } from '../../../src/typedRoutes/api/common/login'; +import { LoginRequest, PostV1Login } from '../../../src/typedRoutes/api/v1/login'; +import { PostV2Login } from '../../../src/typedRoutes/api/v2/login'; import { assertDecode } from './common'; import 'should'; import 'should-http'; @@ -130,7 +131,7 @@ describe('Login codec tests', function () { }); describe('LoginResponse', function () { - const LoginResponse = PostLogin.response[200]; + const LoginResponse = PostV1Login.response[200]; it('should validate response with all required fields', function () { const validResponse = { @@ -245,24 +246,41 @@ describe('Login codec tests', function () { }); }); - describe('PostLogin route definition', function () { + describe('PostV1Login route definition', function () { it('should have the correct path', function () { - assert.strictEqual(PostLogin.path, '/api/v[12]/user/login'); + assert.strictEqual(PostV1Login.path, '/api/v1/user/login'); }); it('should have the correct HTTP method', function () { - assert.strictEqual(PostLogin.method, 'POST'); + assert.strictEqual(PostV1Login.method, 'POST'); }); it('should have the correct request configuration', function () { - // Verify the route is configured with a request property - assert.ok(PostLogin.request); + assert.ok(PostV1Login.request); }); it('should have the correct response types', function () { - // Check that the response object has the expected status codes - assert.ok(PostLogin.response[200]); - assert.ok(PostLogin.response[404]); + assert.ok(PostV1Login.response[200]); + assert.ok(PostV1Login.response[404]); + }); + }); + + describe('PostV2Login route definition', function () { + it('should have the correct path', function () { + assert.strictEqual(PostV2Login.path, '/api/v2/user/login'); + }); + + it('should have the correct HTTP method', function () { + assert.strictEqual(PostV2Login.method, 'POST'); + }); + + it('should have the correct request configuration', function () { + assert.ok(PostV2Login.request); + }); + + it('should have the correct response types', function () { + assert.ok(PostV2Login.response[200]); + assert.ok(PostV2Login.response[404]); }); }); @@ -304,7 +322,7 @@ describe('Login codec tests', function () { assert.strictEqual(result.body.email, mockLoginResponse.email); assert.strictEqual(result.body.forceSMS, mockLoginResponse.forceSMS); - const decodedResponse = assertDecode(PostLogin.response[200], result.body); + const decodedResponse = assertDecode(PostV1Login.response[200], result.body); assert.strictEqual(decodedResponse.email, mockLoginResponse.email); }); @@ -329,7 +347,7 @@ describe('Login codec tests', function () { assert.strictEqual(result.body.email, mockLoginResponse.email); assert.strictEqual(result.body.forceSMS, mockLoginResponse.forceSMS); - const decodedResponse = assertDecode(PostLogin.response[200], result.body); + const decodedResponse = assertDecode(PostV2Login.response[200], result.body); assert.strictEqual(decodedResponse.email, mockLoginResponse.email); }); @@ -375,7 +393,7 @@ describe('Login codec tests', function () { result.body.should.have.property('forceSMS'); result.body.should.have.property('extensible'); - const decodedResponse = assertDecode(PostLogin.response[200], result.body); + const decodedResponse = assertDecode(PostV1Login.response[200], result.body); assert.strictEqual(decodedResponse.email, mockFullResponse.email); assert.strictEqual(decodedResponse.extensible, mockFullResponse.extensible); assert.strictEqual(decodedResponse.initialHash, mockFullResponse.initialHash); @@ -423,7 +441,7 @@ describe('Login codec tests', function () { result.body.should.have.property('forceSMS'); result.body.should.have.property('extensible'); - const decodedResponse = assertDecode(PostLogin.response[200], result.body); + const decodedResponse = assertDecode(PostV2Login.response[200], result.body); assert.strictEqual(decodedResponse.email, mockFullResponse.email); assert.strictEqual(decodedResponse.extensible, mockFullResponse.extensible); assert.strictEqual(decodedResponse.initialHash, mockFullResponse.initialHash); @@ -447,7 +465,7 @@ describe('Login codec tests', function () { result.body.should.have.property('email'); result.body.should.have.property('forceSMS'); - const decodedResponse = assertDecode(PostLogin.response[200], result.body); + const decodedResponse = assertDecode(PostV2Login.response[200], result.body); assert.ok(decodedResponse); }); });