Skip to content

Commit 9361228

Browse files
Added function to retry login with refresh JWT token (#69)
* Added function to retry login with refresh token * Using callback/closure approach for repeated operations
1 parent 62ae0fe commit 9361228

File tree

14 files changed

+215
-51
lines changed

14 files changed

+215
-51
lines changed

lib/client.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ export declare class DgraphClient {
1313
debug(msg: string): void;
1414
anyClient(): DgraphClientStub;
1515
}
16+
export declare function isJwtExpired(err: any): Boolean;
1617
export declare function deleteEdges(mu: types.Mutation, uid: string, ...predicates: string[]): void;

lib/client.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,36 @@ var DgraphClient = (function () {
5454
}
5555
DgraphClient.prototype.alter = function (op, metadata, options) {
5656
return __awaiter(this, void 0, void 0, function () {
57-
var c, pl, _a, _b;
58-
return __generator(this, function (_c) {
59-
switch (_c.label) {
57+
var c, payload, operation, e_1, pl;
58+
var _this = this;
59+
return __generator(this, function (_a) {
60+
switch (_a.label) {
6061
case 0:
6162
this.debug("Alter request:\n" + util_1.stringifyMessage(op));
6263
c = this.anyClient();
63-
_b = (_a = types).createPayload;
64-
return [4, c.alter(op, metadata, options)];
64+
operation = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
65+
return [2, c.alter(op, metadata, options)];
66+
}); }); };
67+
_a.label = 1;
6568
case 1:
66-
pl = _b.apply(_a, [_c.sent()]);
69+
_a.trys.push([1, 3, , 7]);
70+
return [4, operation()];
71+
case 2:
72+
payload = _a.sent();
73+
return [3, 7];
74+
case 3:
75+
e_1 = _a.sent();
76+
if (!(isJwtExpired(e_1) === true)) return [3, 6];
77+
return [4, c.retryLogin(metadata, options)];
78+
case 4:
79+
_a.sent();
80+
return [4, operation()];
81+
case 5:
82+
payload = _a.sent();
83+
_a.label = 6;
84+
case 6: return [3, 7];
85+
case 7:
86+
pl = types.createPayload(payload);
6787
this.debug("Alter response:\n" + util_1.stringifyMessage(pl));
6888
return [2, pl];
6989
}
@@ -88,6 +108,13 @@ var DgraphClient = (function () {
88108
return DgraphClient;
89109
}());
90110
exports.DgraphClient = DgraphClient;
111+
function isJwtExpired(err) {
112+
if (!err) {
113+
return false;
114+
}
115+
return util_1.isUnauthenticatedError(err);
116+
}
117+
exports.isJwtExpired = isJwtExpired;
91118
function deleteEdges(mu, uid) {
92119
var predicates = [];
93120
for (var _i = 2; _i < arguments.length; _i++) {

lib/clientStub.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export declare class DgraphClientStub {
88
constructor(addr?: string, credentials?: grpc.ChannelCredentials, options?: object);
99
login(userid?: string, password?: string, refreshJwt?: string, metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.Jwt>;
1010
alter(op: messages.Operation, metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.Payload>;
11+
retryLogin(metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.Jwt>;
1112
query(req: messages.Request, metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.Response>;
1213
mutate(mu: messages.Mutation, metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.Response>;
1314
commitOrAbort(ctx: messages.TxnContext, metadata?: grpc.Metadata, options?: grpc.CallOptions): Promise<messages.TxnContext>;

lib/clientStub.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3838
var grpc = require("grpc");
3939
var services = require("../generated/api_grpc_pb");
4040
var messages = require("../generated/api_pb");
41+
var errors_1 = require("./errors");
4142
var util_1 = require("./util");
4243
var DgraphClientStub = (function () {
4344
function DgraphClientStub(addr, credentials, options) {
@@ -91,6 +92,28 @@ var DgraphClientStub = (function () {
9192
DgraphClientStub.prototype.alter = function (op, metadata, options) {
9293
return this.promisified.alter(op, this.ensureMetadata(metadata), ensureCallOptions(options));
9394
};
95+
DgraphClientStub.prototype.retryLogin = function (metadata, options) {
96+
return __awaiter(this, void 0, void 0, function () {
97+
var req, resp, jwtResponse;
98+
return __generator(this, function (_a) {
99+
switch (_a.label) {
100+
case 0:
101+
if (this.refreshJwt.length === 0) {
102+
throw errors_1.ERR_REFRESH_JWT_EMPTY;
103+
}
104+
req = new messages.LoginRequest();
105+
req.setRefreshToken(this.refreshJwt);
106+
return [4, this.promisified.login(req, this.ensureMetadata(metadata), ensureCallOptions(options))];
107+
case 1:
108+
resp = _a.sent();
109+
jwtResponse = messages.Jwt.deserializeBinary(resp.getJson_asU8());
110+
this.accessJwt = jwtResponse.getAccessJwt();
111+
this.refreshJwt = jwtResponse.getRefreshJwt();
112+
return [2, jwtResponse];
113+
}
114+
});
115+
});
116+
};
94117
DgraphClientStub.prototype.query = function (req, metadata, options) {
95118
return this.promisified.query(req, this.ensureMetadata(metadata), ensureCallOptions(options));
96119
};

lib/errors.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export declare const ERR_FINISHED: Error;
33
export declare const ERR_ABORTED: Error;
44
export declare const ERR_BEST_EFFORT_REQUIRED_READ_ONLY: Error;
55
export declare const ERR_READ_ONLY: Error;
6+
export declare const ERR_REFRESH_JWT_EMPTY: Error;

lib/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ exports.ERR_FINISHED = new Error("Transaction has already been committed or disc
55
exports.ERR_ABORTED = new Error("Transaction has been aborted. Please retry");
66
exports.ERR_BEST_EFFORT_REQUIRED_READ_ONLY = new Error("Best effort only works for read-only queries");
77
exports.ERR_READ_ONLY = new Error("Readonly transaction cannot run mutations or be committed");
8+
exports.ERR_REFRESH_JWT_EMPTY = new Error("refresh jwt should not be empty");

lib/txn.js

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
4747
};
4848
Object.defineProperty(exports, "__esModule", { value: true });
4949
var messages = require("../generated/api_pb");
50+
var client_1 = require("./client");
5051
var errors_1 = require("./errors");
5152
var types = require("./types");
5253
var util_1 = require("./util");
@@ -109,9 +110,10 @@ var Txn = (function () {
109110
};
110111
Txn.prototype.doRequest = function (req, metadata, options) {
111112
return __awaiter(this, void 0, void 0, function () {
112-
var mutationList, resp, c, _a, _b, e_1, e_2;
113-
return __generator(this, function (_c) {
114-
switch (_c.label) {
113+
var mutationList, resp, c, operation, _a, _b, e_1, _c, _d, e_2;
114+
var _this = this;
115+
return __generator(this, function (_e) {
116+
switch (_e.label) {
115117
case 0:
116118
mutationList = req.getMutationsList();
117119
if (this.finished) {
@@ -129,28 +131,40 @@ var Txn = (function () {
129131
req.setStartTs(this.ctx.getStartTs());
130132
this.dc.debug("Do request:\n" + util_1.stringifyMessage(req));
131133
c = this.dc.anyClient();
132-
_c.label = 1;
134+
operation = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
135+
return [2, c.query(req, metadata, options)];
136+
}); }); };
137+
_e.label = 1;
133138
case 1:
134-
_c.trys.push([1, 3, , 8]);
139+
_e.trys.push([1, 3, , 11]);
135140
_b = (_a = types).createResponse;
136-
return [4, c.query(req, metadata, options)];
141+
return [4, operation()];
137142
case 2:
138-
resp = _b.apply(_a, [_c.sent()]);
139-
return [3, 8];
143+
resp = _b.apply(_a, [_e.sent()]);
144+
return [3, 11];
140145
case 3:
141-
e_1 = _c.sent();
142-
_c.label = 4;
146+
e_1 = _e.sent();
147+
if (!(client_1.isJwtExpired(e_1) === true)) return [3, 6];
148+
return [4, c.retryLogin(metadata, options)];
143149
case 4:
144-
_c.trys.push([4, 6, , 7]);
145-
return [4, this.discard(metadata, options)];
150+
_e.sent();
151+
_d = (_c = types).createResponse;
152+
return [4, operation()];
146153
case 5:
147-
_c.sent();
148-
return [3, 7];
154+
resp = _d.apply(_c, [_e.sent()]);
155+
return [3, 10];
149156
case 6:
150-
e_2 = _c.sent();
151-
return [3, 7];
152-
case 7: throw (util_1.isAbortedError(e_1) || util_1.isConflictError(e_1)) ? errors_1.ERR_ABORTED : e_1;
157+
_e.trys.push([6, 8, , 9]);
158+
return [4, this.discard(metadata, options)];
159+
case 7:
160+
_e.sent();
161+
return [3, 9];
153162
case 8:
163+
e_2 = _e.sent();
164+
return [3, 9];
165+
case 9: throw (util_1.isAbortedError(e_1) || util_1.isConflictError(e_1)) ? errors_1.ERR_ABORTED : e_1;
166+
case 10: return [3, 11];
167+
case 11:
154168
if (req.getCommitNow()) {
155169
this.finished = true;
156170
}
@@ -163,7 +177,8 @@ var Txn = (function () {
163177
};
164178
Txn.prototype.commit = function (metadata, options) {
165179
return __awaiter(this, void 0, void 0, function () {
166-
var c, e_3;
180+
var c, operation, e_3;
181+
var _this = this;
167182
return __generator(this, function (_a) {
168183
switch (_a.label) {
169184
case 0:
@@ -175,24 +190,37 @@ var Txn = (function () {
175190
return [2];
176191
}
177192
c = this.dc.anyClient();
193+
operation = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
194+
return [2, c.commitOrAbort(this.ctx, metadata, options)];
195+
}); }); };
178196
_a.label = 1;
179197
case 1:
180-
_a.trys.push([1, 3, , 4]);
181-
return [4, c.commitOrAbort(this.ctx, metadata, options)];
198+
_a.trys.push([1, 3, , 8]);
199+
return [4, operation()];
182200
case 2:
183201
_a.sent();
184-
return [3, 4];
202+
return [3, 8];
185203
case 3:
186204
e_3 = _a.sent();
187-
throw util_1.isAbortedError(e_3) ? errors_1.ERR_ABORTED : e_3;
188-
case 4: return [2];
205+
if (!(client_1.isJwtExpired(e_3) === true)) return [3, 6];
206+
return [4, c.retryLogin(metadata, options)];
207+
case 4:
208+
_a.sent();
209+
return [4, operation()];
210+
case 5:
211+
_a.sent();
212+
return [3, 7];
213+
case 6: throw util_1.isAbortedError(e_3) ? errors_1.ERR_ABORTED : e_3;
214+
case 7: return [3, 8];
215+
case 8: return [2];
189216
}
190217
});
191218
});
192219
};
193220
Txn.prototype.discard = function (metadata, options) {
194221
return __awaiter(this, void 0, void 0, function () {
195-
var c;
222+
var c, operation, e_4;
223+
var _this = this;
196224
return __generator(this, function (_a) {
197225
switch (_a.label) {
198226
case 0:
@@ -205,10 +233,28 @@ var Txn = (function () {
205233
}
206234
this.ctx.setAborted(true);
207235
c = this.dc.anyClient();
208-
return [4, c.commitOrAbort(this.ctx, metadata, options)];
236+
operation = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
237+
return [2, c.commitOrAbort(this.ctx, metadata, options)];
238+
}); }); };
239+
_a.label = 1;
209240
case 1:
241+
_a.trys.push([1, 3, , 7]);
242+
return [4, operation()];
243+
case 2:
244+
_a.sent();
245+
return [3, 7];
246+
case 3:
247+
e_4 = _a.sent();
248+
if (!(client_1.isJwtExpired(e_4) === true)) return [3, 6];
249+
return [4, c.retryLogin(metadata, options)];
250+
case 4:
251+
_a.sent();
252+
return [4, operation()];
253+
case 5:
210254
_a.sent();
211-
return [2];
255+
_a.label = 6;
256+
case 6: return [3, 7];
257+
case 7: return [2];
212258
}
213259
});
214260
});

lib/util.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export declare function errorCode(err: any): {
55
};
66
export declare function isAbortedError(err: any): boolean;
77
export declare function isConflictError(err: any): boolean;
8+
export declare function isUnauthenticatedError(err: any): boolean;
89
export declare function promisify1<A, T>(f: (arg: A, cb: (err?: Error, res?: T) => void) => void, thisContext?: any): (arg: A) => Promise<T>;
910
export declare function promisify3<A, B, C, T>(f: (argA: A, argB: B, argC: C, cb: (err?: Error, res?: T) => void) => void, thisContext?: any): (argA: A, argB: B, argC: C) => Promise<T>;
1011
export declare function stringifyMessage(msg: jspb.Message): string;

lib/util.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ function isConflictError(err) {
2727
return ec.valid && (ec.code === grpc.status.ABORTED || ec.code === grpc.status.FAILED_PRECONDITION);
2828
}
2929
exports.isConflictError = isConflictError;
30+
function isUnauthenticatedError(err) {
31+
var ec = errorCode(err);
32+
return ec.valid && (ec.code === grpc.status.UNAUTHENTICATED);
33+
}
34+
exports.isUnauthenticatedError = isUnauthenticatedError;
3035
function promisify1(f, thisContext) {
3136
return function (arg) {
3237
return new Promise(function (resolve, reject) {

src/client.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { DgraphClientStub } from "./clientStub";
66
import { ERR_NO_CLIENTS } from "./errors";
77
import { Txn, TxnOptions } from "./txn";
88
import * as types from "./types";
9-
import { stringifyMessage } from "./util";
9+
import { isUnauthenticatedError, stringifyMessage } from "./util";
1010

1111
/**
1212
* Client is a transaction aware client to a set of Dgraph server instances.
@@ -43,7 +43,17 @@ export class DgraphClient {
4343
this.debug(`Alter request:\n${stringifyMessage(op)}`);
4444

4545
const c = this.anyClient();
46-
const pl = types.createPayload(await c.alter(op, metadata, options));
46+
let payload: messages.Payload;
47+
const operation = async () => c.alter(op, metadata, options);
48+
try {
49+
payload = await operation();
50+
} catch (e) {
51+
if (isJwtExpired(e) === true) {
52+
await c.retryLogin(metadata, options);
53+
payload = await operation();
54+
}
55+
}
56+
const pl = types.createPayload(payload);
4757
this.debug(`Alter response:\n${stringifyMessage(pl)}`);
4858

4959
return pl;
@@ -79,6 +89,14 @@ export class DgraphClient {
7989
}
8090
}
8191

92+
// isJwtExpired returns true if the error indicates that the jwt has expired.
93+
export function isJwtExpired(err: any): Boolean { // tslint:disable-line no-any
94+
if (!err) {
95+
return false;
96+
}
97+
return isUnauthenticatedError(err);
98+
}
99+
82100
/**
83101
* deleteEdges sets the edges corresponding to predicates on the node with the
84102
* given uid for deletion.

0 commit comments

Comments
 (0)