avatarMarcos Henrique da Silva

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

18134

Abstract

ord">import</span> {JwtMiddleware} <span class="hljs-keyword">from</span> <span class="hljs-string">'../auth/middlewares/jwt.middleware'</span>;</pre></div><div id="d3ef"><pre><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;</pre></div><div id="c3e2"><pre><span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UsersRoutes</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">CommonRoutesConfig</span> <span class="hljs-title">implements</span> <span class="hljs-title">configureRoutes</span> </span>{ constructor(app: express.<span class="hljs-type">Application</span>) { <span class="hljs-keyword">super</span>(app, '<span class="hljs-type">UsersRoute</span>'); <span class="hljs-keyword">this</span>.configureRoutes(); }</pre></div><div id="6702"><pre><span class="hljs-title function_">configureRoutes</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">const</span> usersController = <span class="hljs-keyword">new</span> <span class="hljs-title class_">UsersController</span>(); <span class="hljs-keyword">const</span> usersMiddleware = <span class="hljs-title class_">UsersMiddleware</span>.<span class="hljs-title function_">getInstance</span>(); <span class="hljs-keyword">const</span> jwtMiddleware = <span class="hljs-title class_">JwtMiddleware</span>.<span class="hljs-title function_">getInstance</span>(); <span class="hljs-keyword">const</span> commonPermissionMiddleware = <span class="hljs-keyword">new</span> <span class="hljs-title class_">CommonPermissionMiddleware</span>(); <span class="hljs-variable language_">this</span>.<span class="hljs-property">app</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">/users</span>, [ jwtMiddleware.<span class="hljs-property">validJWTNeeded</span>, commonPermissionMiddleware.<span class="hljs-property">onlyAdminCanDoThisAction</span>, usersController.<span class="hljs-property">listUsers</span> ]);</pre></div><div id="a792"><pre>this.app.post(/users, [ usersMiddleware.validateRequiredCreateUserBodyFields, usersMiddleware.validateSameEmailDoesntExist, usersController.createUser ])<span class="hljs-comment">;</span></pre></div><div id="6fa0"><pre>this.app.<span class="hljs-keyword">put</span>(/users/:userId, [ jwtMiddleware.validJWTNeeded, commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION), commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction, usersMiddleware.validateUserExists, usersMiddleware.extractUserId, usersController.<span class="hljs-keyword">put</span> ]);</pre></div><div id="0647"><pre>this<span class="hljs-selector-class">.app</span><span class="hljs-selector-class">.patch</span>(/users/:userId, [ jwtMiddleware.validJWTNeeded, commonPermissionMiddleware.<span class="hljs-built_in">minimumPermissionLevelRequired</span>(CommonPermissionMiddleware.BASIC_PERMISSION), commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction, usersMiddleware.validateUserExists, usersMiddleware.extractUserId, usersController.patch ]);</pre></div><div id="e6ff"><pre><span class="hljs-keyword">this</span>.app.<span class="hljs-keyword">delete</span>(<span class="hljs-string">/users/:userId</span>, [ jwtMiddleware.validJWTNeeded, commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION), commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction, usersMiddleware.validateUserExists, usersMiddleware.extractUserId, usersController.removeUser ]); <span class="hljs-keyword">this</span>.app.get(<span class="hljs-string">/users/:userId</span>, [ jwtMiddleware.validJWTNeeded, commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION), commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction, usersMiddleware.validateUserExists, usersMiddleware.extractUserId, usersController.getUserById ]); }</pre></div><div id="597f"><pre>}</pre></div><p id="9761">We added the middlewares and configured it for our code demonstration.</p><p id="edda">At our auth module, let’s update the <b>app/auth/middlewares/auth.middleware.ts </b>file<b>:</b></p><div id="62ca"><pre><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>; <span class="hljs-keyword">import</span> {UsersService} <span class="hljs-keyword">from</span> <span class="hljs-string">'../../users/services/user.services'</span>; <span class="hljs-keyword">import</span> {SecurePass} <span class="hljs-keyword">from</span> <span class="hljs-string">'argon2-pass'</span>;</pre></div><div id="827d"><pre><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AuthMiddleware</span> { <span class="hljs-keyword">private</span> <span class="hljs-type">static</span> instance: AuthMiddleware;</pre></div><div id="789b"><pre><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-title">getInstance</span>()</span> { <span class="hljs-keyword">if</span> (!AuthMiddleware.instance) { AuthMiddleware.instance = <span class="hljs-keyword">new</span> AuthMiddleware(); } <span class="hljs-keyword">return</span> AuthMiddleware.instance; }</pre></div><div id="be4a"><pre>async <span class="hljs-built_in">validateBodyRequest</span>(req: express.Request, res: express.Response, next: express.NextFunction) { <span class="hljs-built_in">if</span>(req.body && req.body.email && req.body.password){ <span class="hljs-built_in">next</span>(); }else{ res<span class="hljs-selector-class">.status</span>(<span class="hljs-number">400</span>)<span class="hljs-selector-class">.send</span>({error: 'Missing body fields: email, password'}); } }</pre></div><div id="7f4b"><pre>async verifyUserPassword(req: express.Request, res: express.Response, next: express.NextFunction) { <span class="hljs-built_in"> const </span>userService = UsersService.getInstance(); <span class="hljs-built_in"> const </span>user: any = await userService.getByEmail(req.body.email); <span class="hljs-built_in"> if </span>(user) { let passwordHash = user.password; <span class="hljs-built_in"> const </span>sp =<span class="hljs-built_in"> new </span>SecurePass(); <span class="hljs-built_in"> const </span>passwordBuffer = Buffer.from(passwordHash, 'utf8'); <span class="hljs-built_in"> const </span>requestPassword = Buffer.from(req.body.password, 'utf8'); <span class="hljs-built_in"> const </span>result = await sp.verifyHash(requestPassword, passwordBuffer); <span class="hljs-built_in"> if </span>(SecurePass.isValid(result)) { req.body = { userId: user._id, email: user.email, provider: 'email', permissionLevel: user.permissionLevel, }; <span class="hljs-built_in"> return </span>next(); } else { res.status(400).send({errors: Invalid e-mail<span class="hljs-built_in"> and/or </span>password}); } } else { res.status(400).send({errors: Invalid e-mail<span class="hljs-built_in"> and/or </span>password}); } } }</pre></div><p id="1dbc">Now we are sending the permissionLevel to the jwt that will be generated.</p><p id="581f">Before testing it, we will create a service to help our e2e testing that will generate some JWT for us. at <b>app/auth/services</b> folder (if doesn’t exist, let’s create it) let’s create a file called <b>jwt.service.ts </b>and add the following:</p><div id="7e89"><pre><span class="hljs-comment">// todo: move to a secure place</span> <span class="hljs-keyword">const</span> jwtSecret = <span class="hljs-string">'My!@!Se3cr8tH4sh'</span>; <span class="hljs-keyword">const</span> tokenExpirationInSeconds = <span class="hljs-number">36000</span>; <span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>); <span class="hljs-keyword">let</span> crypto = <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>); <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">JwtService</span>{ <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title function_">generateToken</span>(<span class="hljs-params">permissionLevel: <span class="hljs-built_in">Number</span></span>) { <span class="hljs-keyword">try</span> {</pre></div><div id="dbec"><pre><span class="hljs-built_in">let</span> refreshId = <span class="hljs-string">'123321'</span> + jwtSecret; <span class="hljs-built_in">let</span> salt = crypto.randomBytes(16).toString(<span class="hljs-string">'base64'</span>); <span class="hljs-built_in">let</span> <span class="hljs-built_in">hash</span> = crypto.createHmac(<span class="hljs-string">'sha512'</span>, salt).update(refreshId).digest(<span class="hljs-string">"base64"</span>);</pre></div><div id="f92c"><pre>let expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(); expiresAt.<span class="hljs-title function_ invoke__">setHours</span>(expiresAt.<span class="hljs-title function_ invoke__">getHours</span>() + (tokenExpirationInSeconds / <span class="hljs-number">3600</span>)); let token = jwt.<span class="hljs-title function_ invoke__">sign</span>({ <span class="hljs-attr">userId</span>: <span class="hljs-string">'007'</span>, <span class="hljs-attr">email</span>: <span class="hljs-string">'[email protected]'</span>, <span class="hljs-attr">permissionLevel</span>: permissionLevel, <span class="hljs-attr">provider</span>: <span class="hljs-string">'email'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'bond'</span>, <span class="hljs-attr">refreshKey</span>: salt }, jwtSecret); let b = Buffer.<span class="hljs-keyword">from</span>(hash); let refreshToken = b.<span class="hljs-title function_ invoke__">toString</span>(<span class="hljs-string">'base64'</span>); <span class="hljs-keyword">return</span> token; } <span class="hljs-keyword">catch</span> (err) { <span class="hljs-keyword">throw</span> err; } } }</pre></div><h2 id="bf9d">Updating and testing our e2e integration</h2><p id="9ef7">Our code is already ready to go, but since we have our e2e testing integration, let’s update our tests!</p><p id="2023"><b>/test/users/users.test.ts</b></p><div id="e9bb"><pre><span class="hljs-keyword">import</span> app <span class="hljs-keyword">from</span> <span class="hljs-string">'../../app/app'</span>; <span class="hljs-keyword">import</span> {agent <span class="hljs-keyword">as</span> request} <span class="hljs-keyword">from</span> <span class="hljs-string">'supertest'</span>; <span class="hljs-keyword">import</span> {expect} <span class="hljs-keyword">from</span> <span class="hljs-string">'chai'</span>; <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> shortUUID <span class="hljs-keyword">from</span> <span class="hljs-string">"short-uuid"</span>; <span class="hljs-keyword">import</span> {<span class="hljs-title class_">JwtService</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">'../../app/auth/services/jwt.service'</span>; <span class="hljs-keyword">let</span> firstUserIdTest = <span class="hljs-string">''</span>; <span class="hljs-keyword">let</span> firstUserBody = { <span class="hljs-string">"name"</span> : <span class="hljs-string">"Marcos SIlva"</span>, <span class="hljs-string">"email"</span> : <span class="hljs-string">tio.makin+<span class="hljs-subst">${shortUUID.generate()}</span>@gmail.com</span>, <span class="hljs-string">"password"</span> : <span class="hljs-string">"Pass#your!word"</span> };</pre></div><div id="97ea"><pre><span class="hljs-attribute">let jwt</span> = { accessToken: <span class="hljs-string">''</span>, refreshToken: <span class="hljs-string">''</span> };</pre></div><div id="8cda"><pre>const adminJWT <span class="hljs-operator">=</span> JwtService.generateToken(<span class="hljs-number">2147483647</span>)<span class="hljs-comment">;</span></pre></div><div id="8c20"><pre>it('should POST /users', async function () { const res = await request(<span class="hljs-name">app</span>) .post('/users').send(<span class="hljs-name">firstUserBody</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">201</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body._id).to.be.an('string')<span class="hljs-comment">;</span> firstUserIdTest = res.body._id<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="7cea"><pre>it(should POST to /auth and retrieve an access token, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth').send({ <span class="hljs-string">"email"</span> : firstUserBody.email, <span class="hljs-string">"password"</span> : firstUserBody.password })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">201</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.accessToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.refreshToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> jwt.accessToken = res.body.accessToken<span class="hljs-comment">;</span> jwt.refreshToken = res.body.refreshToken<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="04e6"><pre>it(should <span class="hljs-built_in">GET</span> /users/:userId, async function () { console.log(Bearer <span class="hljs-variable">${jwt.accessToken}</span>); const res = await request(app) .<span class="hljs-built_in">get</span>(/users/<span class="hljs-variable">${firstUserIdTest}</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Authorization'</span>, Bearer <span class="hljs-variable">${jwt.accessToken}</span>) .send(); expect(res.status).<span class="hljs-keyword">to</span>.equal(200); expect(res.body).<span class="hljs-keyword">not</span>.<span class="hljs-keyword">to</span>.be.empty; expect(res.body).<span class="hljs-keyword">to</span>.be.an(<span class="hljs-string">"object"</span>);</pre></div><div id="df9d"><pre><span class="hljs-built_in">expect</span>(res.body._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.an</span>('string'); <span class="hljs-built_in">expect</span>(res.body.name)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserBody.name); <span class="hljs-built_in">expect</span>(res.body.email)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserBody.email); <span class="hljs-built_in">expect</span>(res.body.permissionLevel)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(<span class="hljs-number">15</span>); <span class="hljs-built_in">expect</span>(res.body._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserIdTest); });</pre></div><div id="bdbe"><pre>it(should <span class="hljs-built_in">GET</span> /users, async function () { const res = await request(app) .<span class="hljs-built_in">get</span>(/users) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Authorization'</span>, Bearer <span class="hljs-variable">${adminJWT}</span>) .send(); expect(res.status).<span class="hljs-keyword">to</span>.equal(200); expect(res.body).<span class="hljs

Options

-keyword">not</span>.<span class="hljs-keyword">to</span>.be.empty; expect(res.body).<span class="hljs-keyword">to</span>.be.an(<span class="hljs-string">"array"</span>);</pre></div><div id="207e"><pre><span class="hljs-built_in">expect</span>(res.body[<span class="hljs-number">0</span>]._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.an</span>('string'); <span class="hljs-built_in">expect</span>(res.body[<span class="hljs-number">0</span>].name)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserBody.name); <span class="hljs-built_in">expect</span>(res.body[<span class="hljs-number">0</span>].email)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserBody.email); <span class="hljs-built_in">expect</span>(res.body[<span class="hljs-number">0</span>]._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserIdTest); });</pre></div><div id="7a23"><pre><span class="hljs-title function_">it</span>(<span class="hljs-string">'should PUT /users/:userId'</span>, <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) { <span class="hljs-keyword">const</span> name = <span class="hljs-string">'Jose'</span>; <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">request</span>(app) .<span class="hljs-title function_">put</span>(<span class="hljs-string">/users/<span class="hljs-subst">${firstUserIdTest}</span></span>) .<span class="hljs-title function_">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-title function_">set</span>(<span class="hljs-string">'Authorization'</span>, <span class="hljs-string">Bearer <span class="hljs-subst">${jwt.accessToken}</span></span>) .<span class="hljs-title function_">send</span>({ <span class="hljs-attr">name</span>: name, <span class="hljs-attr">email</span>: firstUserBody.<span class="hljs-property">email</span> }); <span class="hljs-title function_">expect</span>(res.<span class="hljs-property">status</span>).<span class="hljs-property">to</span>.<span class="hljs-title function_">equal</span>(<span class="hljs-number">204</span>); });</pre></div><div id="de46"><pre>it(should <span class="hljs-built_in">GET</span> /users/:userId <span class="hljs-keyword">to</span> have a new name, async function () { const res = await request(app) .<span class="hljs-built_in">get</span>(/users/<span class="hljs-variable">${firstUserIdTest}</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Authorization'</span>, Bearer <span class="hljs-variable">${jwt.accessToken}</span>) .send(); expect(res.status).<span class="hljs-keyword">to</span>.equal(200); expect(res.body).<span class="hljs-keyword">not</span>.<span class="hljs-keyword">to</span>.be.empty; expect(res.body).<span class="hljs-keyword">to</span>.be.an(<span class="hljs-string">"object"</span>);</pre></div><div id="0eac"><pre><span class="hljs-built_in">expect</span>(res.body._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.an</span>('string'); <span class="hljs-built_in">expect</span>(res.body.name)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.not</span><span class="hljs-selector-class">.equals</span>(firstUserBody.name); <span class="hljs-built_in">expect</span>(res.body.email)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserBody.email); <span class="hljs-built_in">expect</span>(res.body._id)<span class="hljs-selector-class">.to</span><span class="hljs-selector-class">.be</span><span class="hljs-selector-class">.equals</span>(firstUserIdTest); });</pre></div><div id="2b36"><pre><span class="hljs-title function_">it</span>(<span class="hljs-string">'should PATCH /users/:userId'</span>, <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) { <span class="hljs-keyword">let</span> newField = {<span class="hljs-attr">description</span>: <span class="hljs-string">'My user description'</span>}; <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">request</span>(app) .<span class="hljs-title function_">patch</span>(<span class="hljs-string">/users/<span class="hljs-subst">${firstUserIdTest}</span></span>) .<span class="hljs-title function_">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-title function_">set</span>(<span class="hljs-string">'Authorization'</span>, <span class="hljs-string">Bearer <span class="hljs-subst">${jwt.accessToken}</span></span>) .<span class="hljs-title function_">send</span>(newField); <span class="hljs-title function_">expect</span>(res.<span class="hljs-property">status</span>).<span class="hljs-property">to</span>.<span class="hljs-title function_">equal</span>(<span class="hljs-number">204</span>); });</pre></div><div id="96c0"><pre>it(should GET /users/<span class="hljs-symbol">:userId</span> to have a new field called description, async function () { const res = await request(<span class="hljs-name">app</span>) .get(/users/${firstUserIdTest}) .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}) .send()<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">200</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body._id).to.be.an('string')<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.description).to.be.equals('My user description')<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="7ba4"><pre>it('should DELETE /users/<span class="hljs-symbol">:userId</span>', async function () { const res = await request(<span class="hljs-name">app</span>) .delete(/users/${firstUserIdTest}) .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}) .send()<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">204</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="218f"><pre>it(should <span class="hljs-built_in">GET</span> <span class="hljs-built_in">/users </span><span class="hljs-keyword">and</span> receive a 403 <span class="hljs-keyword">for</span> <span class="hljs-keyword">not</span> being an ADMIN, async function () { const res = await request(app) .<span class="hljs-built_in">get</span>(/users) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Accept'</span>, <span class="hljs-string">'application/json'</span>) .<span class="hljs-built_in">set</span>(<span class="hljs-string">'Authorization'</span>, Bearer <span class="hljs-variable">${jwt.accessToken}</span>) .send(); expect(res.status).<span class="hljs-keyword">to</span>.equal(403); });</pre></div><p id="846c"><b>/test/auth/auth.test.ts</b></p><div id="e629"><pre><span class="hljs-keyword">import</span> app <span class="hljs-keyword">from</span> <span class="hljs-string">'../../app/app'</span>; <span class="hljs-keyword">import</span> {agent <span class="hljs-keyword">as</span> request} <span class="hljs-keyword">from</span> <span class="hljs-string">'supertest'</span>; <span class="hljs-keyword">import</span> {expect} <span class="hljs-keyword">from</span> <span class="hljs-string">'chai'</span>; <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> shortUUId <span class="hljs-keyword">from</span> <span class="hljs-string">'short-uuid'</span>;</pre></div><div id="53b6"><pre><span class="hljs-attribute">let firstUserIdTest</span> = <span class="hljs-string">''</span>; <span class="hljs-attribute">let firstUserBody</span> = { <span class="hljs-string">"name"</span>: <span class="hljs-string">"Marcos SIlva"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">tio.makin+auth${shortUUId.generate()}@gmail.com</span>, <span class="hljs-string">"password"</span>: <span class="hljs-string">"Pass#your!word"</span> };</pre></div><div id="55aa"><pre><span class="hljs-attribute">let jwt</span> = { accessToken: <span class="hljs-string">''</span>, refreshToken: <span class="hljs-string">''</span> };</pre></div><div id="8ceb"><pre>it('should POST /users', async function () { const res = await request(<span class="hljs-name">app</span>) .post('/users').send(<span class="hljs-name">firstUserBody</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">201</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body._id).to.be.an('string')<span class="hljs-comment">;</span> firstUserIdTest = res.body._id<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="7720"><pre>it(should POST to /auth and retrieve an access token, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth').send({ <span class="hljs-string">"email"</span> : firstUserBody.email, <span class="hljs-string">"password"</span> : firstUserBody.password })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">201</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.accessToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.refreshToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> jwt.accessToken = res.body.accessToken<span class="hljs-comment">;</span> jwt.refreshToken = res.body.refreshToken<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="1121"><pre>it(should POST to /auth/refresh-token and receive <span class="hljs-number">403</span> for having an invalid JWT, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth/refresh-token') .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}<span class="hljs-number">123123</span>) .send({ <span class="hljs-string">"refreshToken"</span> : jwt.refreshToken })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">403</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="7cb1"><pre>it(should POST to /auth/refresh-token and receive <span class="hljs-number">401</span> for not having a JWT set, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth/refresh-token') .set('Accept', 'application/json') .send({ <span class="hljs-string">"refreshToken"</span> : jwt.refreshToken })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">401</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="a95a"><pre>it(should POST to /auth/refresh-token and receive <span class="hljs-number">400</span> for having an invalid refreshToken, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth/refresh-token') .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}) .send({ <span class="hljs-string">"refreshToken"</span> : '<span class="hljs-number">123</span>' })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">400</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="766d"><pre>it(should POST to /auth/refresh-token and retrieve a new access token, async () => { const res = await request(<span class="hljs-name">app</span>) .post('/auth/refresh-token') .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}) .send({ <span class="hljs-string">"refreshToken"</span> : jwt.refreshToken })<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">201</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).not.to.be.empty<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body).to.be.an(<span class="hljs-string">"object"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.accessToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.body.refreshToken).to.be.an(<span class="hljs-string">"string"</span>)<span class="hljs-comment">;</span> jwt.accessToken = res.body.accessToken<span class="hljs-comment">;</span> jwt.refreshToken = res.body.refreshToken<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="40ce"><pre>it('should DELETE /users/<span class="hljs-symbol">:userId</span>', async function () { const res = await request(<span class="hljs-name">app</span>) .delete(/users/${firstUserIdTest}) .set('Accept', 'application/json') .set('Authorization', Bearer ${jwt.accessToken}) .send()<span class="hljs-comment">;</span> expect(<span class="hljs-name">res</span>.status).to.equal(<span class="hljs-number">204</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><p id="3275">And that’s it, we just need to run our app now with <code>npm run dev</code> and see the updated tests and JWT working. You can try to modify the tests now to not use the JWT or to generate a wrong one and try… then you will start to see the errors!</p><p id="d780">In this article we created and configured a Permission module that works together with our pre-created Auth module. We focused on the code and to make it run and work but remember to read about our <a href="https://en.wikipedia.org/wiki/Bitwise_operation"><b>bitwise AND</b></a> approach to understand why we used it.</p><p id="d8c1">Thanks for reading and see you at the <a href="https://readmedium.com/another-expressjs-api-tutorial-for-2020-part-12-logs-with-winston-df1a20357454">next article</a>!</p><div id="bad0" class="link-block"> <a href="https://readmedium.com/another-expressjs-api-tutorial-for-2020-part-12-logs-with-winston-df1a20357454"> <div> <div> <h2>Another ExpressJS API tutorial for 2020, part 12 — Logs with Winston!</h2> <div><h3>Here is our last article on how to built an ExpressJS API with Typescript. We are going to add longs with Winston to…</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*eWYm3Dm7s0t8lLWYRW4pow.jpeg)"></div> </div> </div> </a> </div><p id="96d4">tips:</p><ul><li>the full code of this article is <a href="https://github.com/makinhs/expressjs-api-tutorial/tree/011-configurando-permissoes">here</a></li><li>don’t forget to use a nice app to test your API such as <a href="https://www.postman.com/">Postman</a> or <a href="https://insomnia.rest/">Insomnia</a></li><li>I know you still didn’t… Read the ExpressJS <a href="https://expressjs.com/en/4x/api.html">documentation</a>!</li><li>Learn Docker, start at their <a href="https://docs.docker.com/get-docker/">Documentation</a>!</li><li>Learn Mongoose, start also at their <a href="https://mongoosejs.com/docs/guide.html">Documentation</a>!</li><li>Complete project is <a href="https://github.com/makinhs/expressjs-api-tutorial">here</a>! (Includes even the non existing articles yet)</li></ul></article></body>

Another ExpressJS API tutorial for 2021, part 11— Configuring Permissions

Hi there, today we are going to configure one of the missing part of our API made with ExpressJS and Typescript, the permission levels to access the API

Hi there, if it is your first time arriving here we are creating a series on how to build an API using ExpressJS and Typescript. The first article can be found here.

Just as a quick overview, here is the series of articles that we are writing:

Articles ago we created the Auth module and in this article we are going to use it at our routes and ensure that the client of our API will use the JWT and will have a proper permissionLevel to use it.

Let’s get started!

First make sure to use our last updated branch that you can find here.

What we want to do is basically add a field called permissionLevel to the users who will generate a JWT at our API. We will use a bitwise AND approach.

Mathematically saying, bitwise AND approach is a very nice tool for us as a developer. I will not go into a deep explanation but I highly recommend you to read about and understand the background of how it works. In a short explanation:

Using a bitwise and approach will allow us to define a powder of 2 integer permissions that you can use to configure which is the required “level”. In the other way, a client would need a sum of the permission levels at his JWT which will be used to validate if the user can/cannot use a specific endpoint request.

That said, let’s pretend that we have the following permissions:

  • 1: free account
  • 2: paid not premium account can use it
  • 4: only a premium account can use it
  • 8: admin access routes

An admin JWT would want to use all routes, so it should have 1 + 2 + 4 + 8 permission level which will be 15. Following the logic, a premium account would be 7, a paid not premium 3, and a free account 1.

But how javascript would understand it? That’s simple, at javascript we are able to use the bitwise AND operator without coding it. For that, we just need to create a if condition and use the operator & (yep, just one and NOT &&) like the following:

if(requiredPermissionLevel & userPermissionLevel){...

Every time that this condition will be true then you can assume that the user have the required permission to access a determined route.

How it will work at the code? Let’s create middlewares for it!

At the folder app/common/middlewares let’s create a filled called common.permission.middleware.ts and add the following:

export class CommonPermissionMiddleware {
public static MAX_PERMISSION = 4096 * 2;
    public static BASIC_PERMISSION = 1;
constructor() {
}
minimumPermissionLevelRequired(requiredPermissionLevel: any) {
        return (req: any, res: any, next: any) => {
            try {
                let userPermissionLevel = parseInt(req.jwt.permissionLevel);
                if (userPermissionLevel & Number.parseInt(requiredPermissionLevel)) {
                    next();
                } else {
                    res.status(403).send({});
                }
            } catch (e) {
                console.log(e);
            }
};
    };
async onlySameUserOrAdminCanDoThisAction(req: any, res: any, next: any) {
        let userPermissionLevel = parseInt(req.jwt.permissionLevel);
        let userId = req.jwt.userId;
        if (req.params && req.params.userId && userId === req.params.userId) {
            return next();
        } else {
            if (userPermissionLevel & CommonPermissionMiddleware.MAX_PERMISSION) {
                return next();
            } else {
                return res.status(403).send({});
            }
        }
    };
async onlyAdminCanDoThisAction(req: any, res: any, next: any) {
        let userPermissionLevel = parseInt(req.jwt.permissionLevel);
        if (userPermissionLevel & CommonPermissionMiddleware.MAX_PERMISSION) {
            return next();
        } else {
            return res.status(403).send({});
        }
    };
}

We created three functions here that will be used whenever we will want. Basically one is a generic when you can add the value that you want (remember, powder of 2 always), then some user routes that an admin can use and finally an admin only middleware. Also, we added a public and static fields that can be shared when we will configure the permissionLevel at our routes.

Remember that we are creating a generic approach here, you can change the permissions as you wish

Now we need to update some code. We need to add a permission to the user, and set this permissionLevel whenever he generates a JWT code.

At our users.controller.ts let’s update the code as the following:

import express from 'express';
import {UsersService} from '../services/user.services';
import {SecurePass} from 'argon2-pass';
export class UsersController {
    constructor() {
    }
async listUsers(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        const users = await usersService.list(100, 0);
        res.status(200).send(users);
    }
async getUserById(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        const user = await usersService.readById(req.params.userId);
        res.status(200).send(user);
    }
async createUser(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        const sp = new SecurePass();
        const password = Buffer.from(req.body.password);
        req.body.password = (await sp.hashPassword(password)).toString('utf-8');
        req.body.permissionLevel = 1 + 2 + 4 + 8;
        const userId = await usersService.create(req.body);
        res.status(201).send({_id: userId});
    }
async patch(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        await usersService.patchById(req.body);
        res.status(204).send(``);
    }
async put(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        await usersService.updateById(req.body);
        res.status(204).send(``);
    }
async removeUser(req: express.Request, res: express.Response) {
        const usersService = UsersService.getInstance();
        await usersService.deleteById(req.params.userId);
        res.status(204).send(``);
    }
}

Here we added the permissionLevel whenever a new user is created with the following:

req.body.permissionLevel = 1 + 2 + 4 + 8;

That means that a new user will always have at least 4 different types of permission level.

Since we are at the users folder, let’s already update the user.routes.config.ts file as the following:

import {CommonRoutesConfig, configureRoutes} from '../common/common.routes.config';
import {UsersController} from './controllers/users.controller';
import {UsersMiddleware} from './middlewares/users.middleware';
import {CommonPermissionMiddleware} from '../common/middlewares/common.permission.middleware';
import {JwtMiddleware} from '../auth/middlewares/jwt.middleware';
import express from 'express';
export class UsersRoutes extends CommonRoutesConfig implements configureRoutes {
    constructor(app: express.Application) {
        super(app, 'UsersRoute');
        this.configureRoutes();
    }
configureRoutes() {
        const usersController = new UsersController();
        const usersMiddleware = UsersMiddleware.getInstance();
        const jwtMiddleware = JwtMiddleware.getInstance();
        const commonPermissionMiddleware = new CommonPermissionMiddleware();
        this.app.get(`/users`, [
            jwtMiddleware.validJWTNeeded,
            commonPermissionMiddleware.onlyAdminCanDoThisAction,
            usersController.listUsers
        ]);
this.app.post(`/users`, [
            usersMiddleware.validateRequiredCreateUserBodyFields,
            usersMiddleware.validateSameEmailDoesntExist,
            usersController.createUser
        ]);
this.app.put(`/users/:userId`, [
            jwtMiddleware.validJWTNeeded,
            commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION),
            commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction,
            usersMiddleware.validateUserExists,
            usersMiddleware.extractUserId,
            usersController.put
        ]);
this.app.patch(`/users/:userId`, [
            jwtMiddleware.validJWTNeeded,
            commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION),
            commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction,
            usersMiddleware.validateUserExists,
            usersMiddleware.extractUserId,
            usersController.patch
        ]);
this.app.delete(`/users/:userId`, [
            jwtMiddleware.validJWTNeeded,
            commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION),
            commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction,
            usersMiddleware.validateUserExists,
            usersMiddleware.extractUserId,
            usersController.removeUser
        ]);
        this.app.get(`/users/:userId`, [
            jwtMiddleware.validJWTNeeded,
            commonPermissionMiddleware.minimumPermissionLevelRequired(CommonPermissionMiddleware.BASIC_PERMISSION),
            commonPermissionMiddleware.onlySameUserOrAdminCanDoThisAction,
            usersMiddleware.validateUserExists,
            usersMiddleware.extractUserId,
            usersController.getUserById
        ]);
    }
}

We added the middlewares and configured it for our code demonstration.

At our auth module, let’s update the app/auth/middlewares/auth.middleware.ts file:

import express from 'express';
import {UsersService} from '../../users/services/user.services';
import {SecurePass} from 'argon2-pass';
export class AuthMiddleware {
    private static instance: AuthMiddleware;
static getInstance() {
        if (!AuthMiddleware.instance) {
            AuthMiddleware.instance = new AuthMiddleware();
        }
        return AuthMiddleware.instance;
    }
async validateBodyRequest(req: express.Request, res: express.Response, next: express.NextFunction) {
        if(req.body && req.body.email && req.body.password){
            next();
        }else{
            res.status(400).send({error: 'Missing body fields: email, password'});
        }
    }
async verifyUserPassword(req: express.Request, res: express.Response, next: express.NextFunction) {
        const userService = UsersService.getInstance();
        const user: any = await userService.getByEmail(req.body.email);
        if (user) {
            let passwordHash = user.password;
            const sp = new SecurePass();
            const passwordBuffer = Buffer.from(passwordHash, 'utf8');
            const requestPassword = Buffer.from(req.body.password, 'utf8');
            const result = await sp.verifyHash(requestPassword, passwordBuffer);
            if (SecurePass.isValid(result)) {
                req.body = {
                    userId: user._id,
                    email: user.email,
                    provider: 'email',
                    permissionLevel: user.permissionLevel,
                };
                return next();
            } else {
                res.status(400).send({errors: `Invalid e-mail and/or password`});
            }
        } else {
            res.status(400).send({errors: `Invalid e-mail and/or password`});
        }
    }
}

Now we are sending the permissionLevel to the jwt that will be generated.

Before testing it, we will create a service to help our e2e testing that will generate some JWT for us. at app/auth/services folder (if doesn’t exist, let’s create it) let’s create a file called jwt.service.ts and add the following:

// todo: move to a secure place
const jwtSecret = 'My!@!Se3cr8tH4sh';
const tokenExpirationInSeconds = 36000;
const jwt = require('jsonwebtoken');
let crypto = require('crypto');
export class JwtService{
    public static generateToken(permissionLevel: Number) {
        try {
let refreshId = '123321' + jwtSecret;
            let salt = crypto.randomBytes(16).toString('base64');
            let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");
let expiresAt = new Date();
            expiresAt.setHours(expiresAt.getHours() + (tokenExpirationInSeconds / 3600));
            let token = jwt.sign({
                userId: '007',
                email: '[email protected]',
                permissionLevel: permissionLevel,
                provider: 'email',
                name: 'bond',
                refreshKey: salt
            }, jwtSecret);
            let b = Buffer.from(hash);
            let refreshToken = b.toString('base64');
            return token;
        } catch (err) {
            throw err;
        }
    }
}

Updating and testing our e2e integration

Our code is already ready to go, but since we have our e2e testing integration, let’s update our tests!

/test/users/users.test.ts

import app from '../../app/app';
import {agent as request} from 'supertest';
import {expect} from 'chai';
import * as shortUUID from "short-uuid";
import {JwtService} from '../../app/auth/services/jwt.service';
let firstUserIdTest = '';
let firstUserBody = {
    "name" : "Marcos SIlva",
    "email" : `tio.makin+${shortUUID.generate()}@gmail.com`,
    "password" : "Pass#your!word"
};
let jwt = {
    accessToken: '',
    refreshToken: ''
};
const adminJWT = JwtService.generateToken(2147483647);
it('should POST /users', async function () {
    const res = await request(app)
        .post('/users').send(firstUserBody);
    expect(res.status).to.equal(201);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body._id).to.be.an('string');
    firstUserIdTest = res.body._id;
});
it(`should POST to /auth and retrieve an access token`, async () => {
    const res = await request(app)
        .post('/auth').send({
            "email" : firstUserBody.email,
            "password" : firstUserBody.password
        });
    expect(res.status).to.equal(201);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body.accessToken).to.be.an("string");
    expect(res.body.refreshToken).to.be.an("string");
    jwt.accessToken = res.body.accessToken;
    jwt.refreshToken = res.body.refreshToken;
});
it(`should GET /users/:userId`, async function () {
    console.log(`Bearer ${jwt.accessToken}`);
    const res = await request(app)
        .get(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(200);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
expect(res.body._id).to.be.an('string');
    expect(res.body.name).to.be.equals(firstUserBody.name);
    expect(res.body.email).to.be.equals(firstUserBody.email);
    expect(res.body.permissionLevel).to.be.equals(15);
    expect(res.body._id).to.be.equals(firstUserIdTest);
});
it(`should GET /users`, async function () {
    const res = await request(app)
        .get(`/users`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${adminJWT}`)
        .send();
    expect(res.status).to.equal(200);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("array");
expect(res.body[0]._id).to.be.an('string');
    expect(res.body[0].name).to.be.equals(firstUserBody.name);
    expect(res.body[0].email).to.be.equals(firstUserBody.email);
    expect(res.body[0]._id).to.be.equals(firstUserIdTest);
});
it('should PUT /users/:userId', async function () {
    const name = 'Jose';
    const res = await request(app)
        .put(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send({
            name: name,
            email: firstUserBody.email
        });
    expect(res.status).to.equal(204);
});
it(`should GET /users/:userId to have a new name`, async function () {
    const res = await request(app)
        .get(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(200);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
expect(res.body._id).to.be.an('string');
    expect(res.body.name).to.be.not.equals(firstUserBody.name);
    expect(res.body.email).to.be.equals(firstUserBody.email);
    expect(res.body._id).to.be.equals(firstUserIdTest);
});
it('should PATCH /users/:userId', async function () {
    let newField = {description: 'My user description'};
    const res = await request(app)
        .patch(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send(newField);
    expect(res.status).to.equal(204);
});
it(`should GET /users/:userId to have a new field called description`, async function () {
    const res = await request(app)
        .get(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(200);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body._id).to.be.an('string');
    expect(res.body.description).to.be.equals('My user description');
});
it('should DELETE /users/:userId', async function () {
    const res = await request(app)
        .delete(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(204);
});
it(`should GET /users and receive a 403 for not being an ADMIN`, async function () {
    const res = await request(app)
        .get(`/users`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(403);
});

/test/auth/auth.test.ts

import app from '../../app/app';
import {agent as request} from 'supertest';
import {expect} from 'chai';
import * as shortUUId from 'short-uuid';
let firstUserIdTest = '';
let firstUserBody = {
    "name": "Marcos SIlva",
    "email": `tio.makin+auth${shortUUId.generate()}@gmail.com`,
    "password": "Pass#your!word"
};
let jwt = {
    accessToken: '',
    refreshToken: ''
};
it('should POST /users', async function () {
    const res = await request(app)
        .post('/users').send(firstUserBody);
    expect(res.status).to.equal(201);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body._id).to.be.an('string');
    firstUserIdTest = res.body._id;
});
it(`should POST to /auth and retrieve an access token`, async () => {
    const res = await request(app)
        .post('/auth').send({
            "email" : firstUserBody.email,
            "password" : firstUserBody.password
        });
    expect(res.status).to.equal(201);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body.accessToken).to.be.an("string");
    expect(res.body.refreshToken).to.be.an("string");
    jwt.accessToken = res.body.accessToken;
    jwt.refreshToken = res.body.refreshToken;
});
it(`should POST to /auth/refresh-token and receive 403 for having an invalid JWT`, async () => {
    const res = await request(app)
        .post('/auth/refresh-token')
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}123123`)
        .send({
            "refreshToken" : jwt.refreshToken
        });
    expect(res.status).to.equal(403);
});
it(`should POST to /auth/refresh-token and receive 401 for not having a JWT set`, async () => {
    const res = await request(app)
        .post('/auth/refresh-token')
        .set('Accept', 'application/json')
        .send({
            "refreshToken" : jwt.refreshToken
        });
    expect(res.status).to.equal(401);
});
it(`should POST to /auth/refresh-token and receive 400 for having an invalid refreshToken`, async () => {
    const res = await request(app)
        .post('/auth/refresh-token')
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send({
            "refreshToken" : '123'
        });
    expect(res.status).to.equal(400);
});
it(`should POST to /auth/refresh-token and retrieve a new access token`, async () => {
    const res = await request(app)
        .post('/auth/refresh-token')
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send({
            "refreshToken" : jwt.refreshToken
        });
    expect(res.status).to.equal(201);
    expect(res.body).not.to.be.empty;
    expect(res.body).to.be.an("object");
    expect(res.body.accessToken).to.be.an("string");
    expect(res.body.refreshToken).to.be.an("string");
    jwt.accessToken = res.body.accessToken;
    jwt.refreshToken = res.body.refreshToken;
});
it('should DELETE /users/:userId', async function () {
    const res = await request(app)
        .delete(`/users/${firstUserIdTest}`)
        .set('Accept', 'application/json')
        .set('Authorization', `Bearer ${jwt.accessToken}`)
        .send();
    expect(res.status).to.equal(204);
});

And that’s it, we just need to run our app now with npm run dev and see the updated tests and JWT working. You can try to modify the tests now to not use the JWT or to generate a wrong one and try… then you will start to see the errors!

In this article we created and configured a Permission module that works together with our pre-created Auth module. We focused on the code and to make it run and work but remember to read about our bitwise AND approach to understand why we used it.

Thanks for reading and see you at the next article!

tips:

  • the full code of this article is here
  • don’t forget to use a nice app to test your API such as Postman or Insomnia
  • I know you still didn’t… Read the ExpressJS documentation!
  • Learn Docker, start at their Documentation!
  • Learn Mongoose, start also at their Documentation!
  • Complete project is here! (Includes even the non existing articles yet)
API
Nodejs
Expressjs
Typescript
Tutorial
Recommended from ReadMedium