Skip to content

Commit 1d2b7b3

Browse files
committed
Make the serialization of tokens more robust
1 parent 74ad5e5 commit 1d2b7b3

File tree

5 files changed

+66
-42
lines changed

5 files changed

+66
-42
lines changed

.changeset/orange-mangos-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@labdigital/federated-token": minor
3+
---
4+
5+
Make the serialization of tokens more robust

src/datasource.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class FederatedGraphQLDataSource<
2121

2222
// TODO: We now blindly sent the access tokens and refresh tokens to all
2323
// federated services. This is something we might want to whitelist.
24-
const token = context.federatedToken.dumpAccessToken();
24+
const token = context.federatedToken.serializeAccessToken();
2525
if (token) {
2626
headers.set("x-access-token", token);
2727
}
@@ -49,7 +49,7 @@ export class FederatedGraphQLDataSource<
4949
if (!context.federatedToken) {
5050
context.federatedToken = new PublicFederatedToken();
5151
}
52-
context.federatedToken.loadAccessToken(token, true);
52+
context.federatedToken.deserializeAccessToken(token, true);
5353
}
5454

5555
const refreshToken = headers.get("x-refresh-token");

src/plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class FederatedAuthPlugin<TContext extends FederatedTokenContext>
1818

1919
const at = request.http?.headers.get("X-Access-Token");
2020
if (at) {
21-
contextValue.federatedToken.loadAccessToken(at);
21+
contextValue.federatedToken.deserializeAccessToken(at);
2222
}
2323

2424
const rt = request.http?.headers.get("X-Refresh-Token");
@@ -40,7 +40,7 @@ export class FederatedAuthPlugin<TContext extends FederatedTokenContext>
4040
const t = contextValue.federatedToken;
4141

4242
if (t.isAccessTokenModified() || t.isValueModified()) {
43-
const val = t.dumpAccessToken();
43+
const val = t.serializeAccessToken();
4444
if (val) {
4545
response.http.headers.set("X-Access-Token", val);
4646
}

src/token.test.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe("FederatedToken", () => {
4646
assert.isTrue(federatedToken.isAccessTokenModified());
4747
});
4848

49-
test("loadAccessToken", () => {
49+
test("deserializeAccessToken", () => {
5050
const at = Buffer.from(
5151
JSON.stringify({
5252
tokens: {
@@ -55,13 +55,15 @@ describe("FederatedToken", () => {
5555
exp: 1234567890,
5656
},
5757
},
58-
value1: "exampleValue1",
59-
value2: "exampleValue2",
58+
values: {
59+
value1: "exampleValue1",
60+
value2: "exampleValue2",
61+
},
6062
})
6163
).toString("base64");
6264

6365
const federatedToken = new FederatedToken();
64-
federatedToken.loadAccessToken(at);
66+
federatedToken.deserializeAccessToken(at);
6567

6668
assert.deepStrictEqual(
6769
federatedToken.tokens.exampleName,
@@ -87,7 +89,7 @@ describe("FederatedToken", () => {
8789
);
8890
});
8991

90-
test("loadAccessToken with trackModified = true", () => {
92+
test("deserializeAccessToken with trackModified = true", () => {
9193
const at = Buffer.from(
9294
JSON.stringify({
9395
tokens: {
@@ -96,13 +98,15 @@ describe("FederatedToken", () => {
9698
exp: 1234567890,
9799
},
98100
},
99-
value1: "exampleValue1",
100-
value2: "exampleValue2",
101+
values: {
102+
value1: "exampleValue1",
103+
value2: "exampleValue2",
104+
}
101105
})
102106
).toString("base64");
103107

104108
const federatedToken = new FederatedToken();
105-
federatedToken.loadAccessToken(at, true);
109+
federatedToken.deserializeAccessToken(at, true);
106110

107111
assert.deepStrictEqual(
108112
federatedToken.tokens.exampleName,
@@ -128,7 +132,7 @@ describe("FederatedToken", () => {
128132
);
129133
});
130134

131-
test("dumpAccessToken", () => {
135+
test("serializeAccessToken", () => {
132136
const federatedToken = new FederatedToken();
133137
federatedToken.setAccessToken("exampleName", {
134138
token: "exampleToken",
@@ -139,20 +143,22 @@ describe("FederatedToken", () => {
139143
value2: "exampleValue2",
140144
};
141145

142-
const expectedDump = Buffer.from(
146+
const serialized = Buffer.from(
143147
JSON.stringify({
144148
tokens: {
145149
exampleName: {
146150
token: "exampleToken",
147151
exp: 1234567890,
148152
},
149153
},
150-
value1: "exampleValue1",
151-
value2: "exampleValue2",
154+
values: {
155+
value1: "exampleValue1",
156+
value2: "exampleValue2",
157+
}
152158
})
153159
).toString("base64");
154160

155-
assert.equal(federatedToken.dumpAccessToken(), expectedDump);
161+
assert.equal(federatedToken.serializeAccessToken(), serialized);
156162
});
157163

158164
test("loadRefreshToken", () => {

src/token.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ export type FederatedTokenContext = {
44
federatedToken?: FederatedToken;
55
};
66

7+
type FederatedTokenValue = {
8+
tokens: Record<string, AccessToken>;
9+
values: Record<string, any>;
10+
};
11+
712
export interface AccessToken {
813
// Expire at, unixtime
914
token: string;
@@ -55,42 +60,50 @@ export class FederatedToken {
5560
return this._valueModified;
5661
}
5762

58-
loadAccessToken(at: string, trackModified = false) {
59-
const token = JSON.parse(Buffer.from(at, "base64").toString("ascii"));
63+
// Return the expire time of the first token that expires
64+
getExpireTime() {
65+
const values = Object.values(this.tokens);
66+
const sorted = values.sort((a, b) => a.exp - b.exp);
67+
return sorted[0].exp;
68+
}
6069

61-
// TODO: Validate json
70+
// serializeAccessToken serializes the tokens into a base64 encoded string
71+
// in order to be sent to downstream services and from the downstream
72+
// services back to the gatewa
73+
// in order to be sent to downstream services and from the downstream
74+
serializeAccessToken(): string | undefined {
75+
const data: FederatedTokenValue = {
76+
tokens: this.tokens,
77+
values: this.values,
78+
};
6279

63-
if (token.refreshTokens) {
64-
throw new Error("Refresh tokens are not allowed in the Access Token");
80+
if (!data) {
81+
return;
6582
}
83+
return Buffer.from(JSON.stringify(data)).toString("base64");
84+
}
85+
86+
// deserializeAccessToken deserializes the tokens from a base64 encoded string
87+
// as received from downstream services
88+
deserializeAccessToken(at: string, trackModified = false) {
89+
const token: FederatedTokenValue = JSON.parse(
90+
Buffer.from(at, "base64").toString("ascii")
91+
);
6692

67-
// Merge tokens into this object
93+
// Merge tokens into this object. Checking for modified tokens
6894
for (const k in token.tokens) {
6995
if (trackModified && !isEqual(this.tokens[k], token.tokens[k])) {
7096
this._accessTokenModified = true;
7197
}
72-
73-
this.tokens[k] = token.tokens[k];
74-
}
75-
76-
this.tokens = token.tokens;
77-
for (const k in token) {
78-
if (k !== "tokens") {
79-
this.values[k] = token[k];
80-
}
8198
}
82-
}
83-
84-
dumpAccessToken(): string | undefined {
85-
const data = {
86-
tokens: this.tokens,
99+
this.tokens = {
100+
...this.tokens,
101+
...token.tokens,
102+
};
103+
this.values = {
87104
...this.values,
105+
...token.values,
88106
};
89-
90-
if (!data) {
91-
return;
92-
}
93-
return Buffer.from(JSON.stringify(data)).toString("base64");
94107
}
95108

96109
loadRefreshToken(value: string, trackModified = false) {

0 commit comments

Comments
 (0)