Skip to content

Commit fe7be9d

Browse files
authored
feat(auth): improve auth events (#746)
* feat(auth): implement before and after logout events * doc: add logout events documentation * feat(auth): implement before and after login events * style: correct eslint errors * ci(docker): correct functional tests * ci(docker): correct documentation tests
1 parent ab62885 commit fe7be9d

File tree

9 files changed

+193
-34
lines changed

9 files changed

+193
-34
lines changed

.ci/start_kuzzle.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
echo "[$(date --rfc-3339 seconds)] - Start Kuzzle stack"
66

7-
docker-compose -f .ci/docker-compose.yml up -d
7+
docker compose -f .ci/docker-compose.yml up -d
88

99
spinner="/"
1010
until $(curl --output /dev/null --silent --head --fail http://localhost:7512); do

.ci/test-docs.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ set -ex
55
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
66
cd "$here"
77

8-
docker-compose -f ./doc/docker-compose.yml pull
9-
docker-compose -f ./doc/docker-compose.yml run doc-tests node index
8+
docker compose -f ./doc/docker-compose.yml pull
9+
docker compose -f ./doc/docker-compose.yml run doc-tests node index
1010
EXIT=$?
11-
docker-compose -f ./doc/docker-compose.yml down
11+
docker compose -f ./doc/docker-compose.yml down

doc/7/essentials/events/index.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,19 @@ Triggered when the current session has been unexpectedly disconnected.
5959
| `websocket/auth-renewal` | The websocket protocol si reconnecting to renew the token. See [Websocket Cookie Authentication](sdk/js/7/protocols/websocket/introduction#cookie-authentication). |
6060
| `user/connection-closed` | The disconnection is done by the user. |
6161
| `network/error` | An network error occured and caused a disconnection. |
62-
## loginAttempt
62+
63+
## beforeLogin
64+
65+
Triggered before login attempt.
66+
67+
## afterLogin
6368

6469
Triggered when a login attempt completes, either with a success or a failure result.
6570

71+
## loginAttempt
72+
73+
Legacy event triggered when a login attempt completes, either with a success or a failure result.
74+
6675
**Callback arguments:**
6776

6877
`@param {object} data`
@@ -72,6 +81,27 @@ Triggered when a login attempt completes, either with a success or a failure res
7281
| `success` | <pre>boolean</pre> | Indicate if login attempt succeed |
7382
| `error` | <pre>string</pre> | Error message when login fail |
7483

84+
## beforeLogout
85+
86+
Triggered before logout attempt.
87+
88+
## afterLogout
89+
90+
Triggered when a logout attempt completes, either with a success or a failure result.
91+
92+
## logoutAttempt
93+
94+
Legacy event triggered when a logout attempt completes, either with a success or a failure result.
95+
96+
**Callback arguments:**
97+
98+
`@param {object} data`
99+
100+
| Property | Type | Description |
101+
| --------- | ------------------ | --------------------------------- |
102+
| `success` | <pre>boolean</pre> | Indicate if logout attempt succeed |
103+
| `error` | <pre>string</pre> | Error message when logout fail |
104+
75105
## networkError
76106

77107
Triggered when the SDK has failed to connect to Kuzzle.

src/Kuzzle.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ export class Kuzzle extends KuzzleEventEmitter {
8484
"discarded",
8585
"disconnected",
8686
"loginAttempt",
87+
"beforeLogin",
88+
"afterLogin",
8789
"logoutAttempt",
90+
"beforeLogout",
91+
"afterLogout",
8892
"networkError",
8993
"offlineQueuePush",
9094
"offlineQueuePop",
@@ -237,6 +241,7 @@ export class Kuzzle extends KuzzleEventEmitter {
237241
this.protocol = protocol;
238242

239243
this._protectedEvents = {
244+
afterLogin: {},
240245
connected: {},
241246
disconnected: {},
242247
error: {},
@@ -350,7 +355,10 @@ export class Kuzzle extends KuzzleEventEmitter {
350355

351356
this._loggedIn = false;
352357

353-
this.on("loginAttempt", async (status) => {
358+
/**
359+
* When successfuly logged in
360+
*/
361+
this.on("afterLogin", async (status) => {
354362
if (status.success) {
355363
this._loggedIn = true;
356364
return;
@@ -369,7 +377,7 @@ export class Kuzzle extends KuzzleEventEmitter {
369377
/**
370378
* When successfuly logged out
371379
*/
372-
this.on("logoutAttempt", (status) => {
380+
this.on("afterLogout", (status) => {
373381
if (status.success) {
374382
this._loggedIn = false;
375383
}
@@ -574,12 +582,14 @@ export class Kuzzle extends KuzzleEventEmitter {
574582
listener: () => void
575583
): this;
576584

585+
on(eventName: "beforeLogout", listener: () => void): this;
577586
on(
578-
eventName: "logoutAttempt",
587+
eventName: "logoutAttempt" | "afterLogout",
579588
listener: (status: { success: true }) => void
580589
): this;
590+
on(eventName: "beforeLogin", listener: () => void): this;
581591
on(
582-
eventName: "loginAttempt",
592+
eventName: "loginAttempt" | "afterLogin",
583593
listener: (data: { success: boolean; error: string }) => void
584594
): this;
585595
on(eventName: "discarded", listener: (request: RequestPayload) => void): this;

src/controllers/Auth.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ export class AuthController extends BaseController {
447447
strategy,
448448
};
449449

450+
this.kuzzle.emit("beforeLogin");
450451
return this.query(request, { queuable: false, timeout: -1, verb: "POST" })
451452
.then((response) => {
452453
if (this.kuzzle.cookieAuthentication) {
@@ -458,16 +459,22 @@ export class AuthController extends BaseController {
458459
error: err.message,
459460
success: false,
460461
});
462+
this.kuzzle.emit("afterLogin", {
463+
error: err.message,
464+
success: false,
465+
});
461466
throw err;
462467
}
463468

464469
this.kuzzle.emit("loginAttempt", { success: true });
470+
this.kuzzle.emit("afterLogin", { success: true });
465471
return;
466472
}
467473

468474
this._authenticationToken = new Jwt(response.result.jwt);
469475

470476
this.kuzzle.emit("loginAttempt", { success: true });
477+
this.kuzzle.emit("afterLogin", { success: true });
471478

472479
return response.result.jwt;
473480
})
@@ -476,6 +483,11 @@ export class AuthController extends BaseController {
476483
error: err.message,
477484
success: false,
478485
});
486+
this.kuzzle.emit("afterLogin", {
487+
error: err.message,
488+
success: false,
489+
});
490+
479491
throw err;
480492
});
481493
}
@@ -485,17 +497,37 @@ export class AuthController extends BaseController {
485497
*
486498
* @see https://docs.kuzzle.io/sdk/js/7/controllers/auth/logout
487499
*/
488-
logout(): Promise<void> {
489-
return this.query(
490-
{
491-
action: "logout",
492-
cookieAuth: this.kuzzle.cookieAuthentication,
493-
},
494-
{ queuable: false, timeout: -1 }
495-
).then(() => {
500+
async logout(): Promise<void> {
501+
this.kuzzle.emit("beforeLogout");
502+
try {
503+
await this.query(
504+
{
505+
action: "logout",
506+
cookieAuth: this.kuzzle.cookieAuthentication,
507+
},
508+
{ queuable: false, timeout: -1 }
509+
);
496510
this._authenticationToken = null;
511+
/**
512+
* @deprecated logout `logoutAttempt` is legacy event. Use afterLogout instead.
513+
*/
497514
this.kuzzle.emit("logoutAttempt", { success: true });
498-
});
515+
this.kuzzle.emit("afterLogout", { success: true });
516+
} catch (error) {
517+
/**
518+
* @deprecated logout `logoutAttempt` is legacy event. Use afterLogout instead.
519+
*/
520+
this.kuzzle.emit("logoutAttempt", {
521+
error: (error as Error).message,
522+
success: false,
523+
});
524+
this.kuzzle.emit("afterLogout", {
525+
error: (error as Error).message,
526+
success: false,
527+
});
528+
529+
throw error;
530+
}
499531
}
500532

501533
/**

src/core/KuzzleEventEmitter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ export type PublicKuzzleEvents =
1616
| "discarded"
1717
| "disconnected"
1818
| "loginAttempt"
19+
| "beforeLogin"
20+
| "afterLogin"
1921
| "logoutAttempt"
22+
| "beforeLogout"
23+
| "afterLogout"
2024
| "networkError"
2125
| "offlineQueuePush"
2226
| "offlineQueuePop"

test/controllers/auth.test.js

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@ const { AuthController } = require("../../src/controllers/Auth");
77
const { User } = require("../../src/core/security/User");
88
const generateJwt = require("../mocks/generateJwt.mock");
99

10+
/**
11+
* Kuzzle interface
12+
*
13+
* @typedef {Object} Kuzzle
14+
* @property {import("sinon").SinonStub} Kuzzle.query
15+
* @property {import("sinon").SinonStub} Kuzzle.emit
16+
* @property {boolean} Kuzzle.cookieAuthentication
17+
*/
18+
1019
describe("Auth Controller", () => {
1120
const options = { opt: "in" };
12-
let jwt, kuzzle;
21+
/** @type {Kuzzle} */
22+
let kuzzle;
23+
let jwt;
1324

1425
beforeEach(() => {
1526
kuzzle = new KuzzleEventEmitter();
@@ -495,17 +506,33 @@ describe("Auth Controller", () => {
495506
});
496507
});
497508

498-
it('should trigger a "loginAttempt" event once the user is logged in', () => {
509+
it("should trigger a login events the user is logged in", async () => {
499510
kuzzle.emit = sinon.stub();
500511

501-
return kuzzle.auth
512+
await kuzzle.auth.login("strategy", credentials, "expiresIn").then(() => {
513+
should(kuzzle.emit).be.calledWith("beforeLogin");
514+
should(kuzzle.emit).be.calledWith("afterLogin", { success: true });
515+
should(kuzzle.emit).be.calledWith("loginAttempt", { success: true });
516+
});
517+
kuzzle.emit.reset();
518+
519+
kuzzle.query.rejects();
520+
await kuzzle.auth
502521
.login("strategy", credentials, "expiresIn")
503-
.then(() => {
504-
should(kuzzle.emit).be.calledOnce().be.calledWith("loginAttempt");
522+
.catch(() => {
523+
should(kuzzle.emit).be.calledWith("beforeLogin");
524+
should(kuzzle.emit).be.calledWith("afterLogin", {
525+
success: false,
526+
error: "Error",
527+
});
528+
should(kuzzle.emit).be.calledWith("loginAttempt", {
529+
success: false,
530+
error: "Error",
531+
});
505532
});
506533
});
507534

508-
it('should trigger a "loginAttempt" event once the user is logged in with cookieAuthentication enabled', () => {
535+
it("should trigger a login events the user is logged in with cookieAuthentication enabled", async () => {
509536
kuzzle.emit = sinon.stub();
510537
kuzzle.cookieAuthentication = true;
511538
kuzzle.query.resolves({
@@ -514,10 +541,26 @@ describe("Auth Controller", () => {
514541
},
515542
});
516543

517-
return kuzzle.auth
518-
.login("strategy", credentials, "expiresIn")
519-
.then(() => {
520-
should(kuzzle.emit).be.calledOnce().be.calledWith("loginAttempt");
544+
await kuzzle.auth.login("strategy", credentials, "expiresIn").then(() => {
545+
should(kuzzle.emit).be.calledWith("beforeLogin");
546+
should(kuzzle.emit).be.calledWith("afterLogin", { success: true });
547+
should(kuzzle.emit).be.calledWith("loginAttempt", { success: true });
548+
});
549+
kuzzle.emit.reset();
550+
551+
kuzzle.query.rejects();
552+
await should(kuzzle.auth.login("strategy", credentials, "expiresIn"))
553+
.be.rejected()
554+
.catch(() => {
555+
should(kuzzle.emit).be.calledWith("beforeLogin");
556+
should(kuzzle.emit).be.calledWith("afterLogin", {
557+
success: false,
558+
error: "Error",
559+
});
560+
should(kuzzle.emit).be.calledWith("loginAttempt", {
561+
success: false,
562+
error: "Error",
563+
});
521564
});
522565
});
523566

@@ -570,6 +613,42 @@ describe("Auth Controller", () => {
570613
should(kuzzle.auth.authenticationToken).be.null();
571614
});
572615
});
616+
617+
// ? Legacy event
618+
it('should trigger a legacy "logoutAttempt" event the user is logged out', async () => {
619+
kuzzle.emit = sinon.stub();
620+
await kuzzle.auth.logout().then(() => {
621+
should(kuzzle.emit).be.calledWith("logoutAttempt", { success: true });
622+
});
623+
kuzzle.emit.reset();
624+
625+
// ? Fail logout
626+
kuzzle.query.rejects();
627+
await kuzzle.auth.logout().catch(() => {
628+
should(kuzzle.emit).be.calledWith("logoutAttempt", {
629+
success: false,
630+
error: "Error",
631+
});
632+
});
633+
});
634+
635+
it("should trigger logout events when the user is logged out", async () => {
636+
kuzzle.emit = sinon.stub();
637+
await kuzzle.auth.logout().then(() => {
638+
should(kuzzle.emit).be.calledWith("beforeLogout");
639+
should(kuzzle.emit).be.calledWith("afterLogout", { success: true });
640+
});
641+
kuzzle.emit.reset();
642+
643+
// ? Fail logout
644+
kuzzle.query.rejects();
645+
await kuzzle.auth.logout().catch(() => {
646+
should(kuzzle.emit).be.calledWith("afterLogout", {
647+
success: false,
648+
error: "Error",
649+
});
650+
});
651+
});
573652
});
574653

575654
describe("#updateMyCredentials", () => {

0 commit comments

Comments
 (0)