@oslokommune/auth-bff 1.5.0 → 1.6.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"oidc-routes.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc-routes.mjs"],"names":[],"mappings":"AAEA,qDASC"}
1
+ {"version":3,"file":"oidc-routes.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc-routes.mjs"],"names":[],"mappings":"AAEA,qDAUC"}
@@ -5,5 +5,6 @@ export function oidcRoutes(oidcMiddleware) {
5
5
  router.get('/auth/callback', oidcMiddleware.callback);
6
6
  router.get('/auth/logout', oidcMiddleware.logout);
7
7
  router.get('/auth/user', oidcMiddleware.user);
8
+ router.get('/auth/front-channel-logout', oidcMiddleware.frontChannelLogout);
8
9
  return router;
9
10
  }
@@ -8,9 +8,10 @@ export class OidcMiddleware {
8
8
  private constructor();
9
9
  get ensureFreshToken(): (req: any, res: any, next: any) => void;
10
10
  get login(): (req: any, res: any) => void;
11
- get callback(): (req: any, res: any) => Promise<void>;
12
- get user(): (req: any, res: any) => Promise<any>;
11
+ get callback(): (req: any, res: any, next: any) => Promise<void>;
12
+ get user(): (req: any, res: any, next: any) => Promise<any>;
13
13
  get logout(): (req: any, res: any) => void;
14
+ get frontChannelLogout(): (req: any, res: any) => Promise<void>;
14
15
  #private;
15
16
  }
16
17
  //# sourceMappingURL=oidc.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oidc.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc.mjs"],"names":[],"mappings":"AAGA;IAeE,oDAIC;IAdD;;;;OAIG;IACH,sBAGC;IAiDD,yBACU,QAAG,EAAE,QAAG,EAAE,SAAI,UAUvB;IAED,cACU,QAAG,EAAE,QAAG,UAuBjB;IAED,iBACgB,QAAG,EAAE,QAAG,mBAmCvB;IAED,aACgB,QAAG,EAAE,QAAG,kBAevB;IAED,eACU,QAAG,EAAE,QAAG,UAYjB;;CACF"}
1
+ {"version":3,"file":"oidc.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc.mjs"],"names":[],"mappings":"AAIA;IAeE,oDAIC;IAdD;;;;OAIG;IACH,sBAGC;IAsDD,yBACU,QAAG,EAAE,QAAG,EAAE,SAAI,UAWvB;IAED,cACU,QAAG,EAAE,QAAG,UAsBjB;IAED,iBACgB,QAAG,EAAE,QAAG,EAAE,SAAI,mBAqC7B;IAED,aACgB,QAAG,EAAE,QAAG,EAAE,SAAI,kBAmB7B;IAED,eACU,QAAG,EAAE,QAAG,UAQjB;IAED,2BACgB,QAAG,EAAE,QAAG,mBAcvB;;CACF"}
@@ -21,6 +21,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
21
21
  var _OidcMiddleware_instances, _OidcMiddleware_clientManager, _OidcMiddleware_config, _OidcMiddleware_refreshPromises, _OidcMiddleware_refreshTokenSet, _OidcMiddleware_getFreshTokenSet;
22
22
  import { generators, TokenSet } from "openid-client";
23
23
  import { OidcClientManager } from "../client.mjs";
24
+ import { redact } from "../utils.js";
24
25
  export class OidcMiddleware {
25
26
  /**
26
27
  * @private
@@ -56,9 +57,10 @@ export class OidcMiddleware {
56
57
  next();
57
58
  }
58
59
  else {
60
+ console.warn(`401: No valid tokenSet in session sid=${redact(req.session.id)}`);
59
61
  res.sendStatus(401);
60
62
  }
61
- });
63
+ }).catch(next);
62
64
  };
63
65
  }
64
66
  get login() {
@@ -83,16 +85,18 @@ export class OidcMiddleware {
83
85
  };
84
86
  }
85
87
  get callback() {
86
- return (req, res) => __awaiter(this, void 0, void 0, function* () {
87
- const params = __classPrivateFieldGet(this, _OidcMiddleware_clientManager, "f").client.callbackParams(req);
88
- const { codeVerifier, stateKey, stateValue } = req.session;
89
- const redirectUri = `${req.protocol}://${req.headers.host}${__classPrivateFieldGet(this, _OidcMiddleware_config, "f").basePath}/auth/callback`;
88
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
90
89
  try {
90
+ const params = __classPrivateFieldGet(this, _OidcMiddleware_clientManager, "f").client.callbackParams(req);
91
+ const { codeVerifier, stateKey, stateValue } = req.session;
92
+ const redirectUri = `${req.protocol}://${req.headers.host}${__classPrivateFieldGet(this, _OidcMiddleware_config, "f").basePath}/auth/callback`;
91
93
  const tokenSet = yield __classPrivateFieldGet(this, _OidcMiddleware_clientManager, "f").client.callback(redirectUri, params, {
92
94
  code_verifier: codeVerifier,
93
95
  state: stateKey
94
96
  });
95
- req.session.tokenSet = new TokenSet(tokenSet);
97
+ req.session.tokenSet = tokenSet;
98
+ const parsedTokenSet = new TokenSet(tokenSet);
99
+ req.session["idp-sid"] = parsedTokenSet.claims().sid;
96
100
  delete req.session.codeVerifier;
97
101
  delete req.session.stateKey;
98
102
  delete req.session.stateValue;
@@ -106,34 +110,40 @@ export class OidcMiddleware {
106
110
  res.redirect(redirectUrl);
107
111
  });
108
112
  }
109
- catch (err) {
110
- console.error(err);
113
+ catch (e) {
114
+ console.error(e);
111
115
  req.session.destroy(() => {
112
- res.status(500).send("Error during callback");
116
+ next(e);
113
117
  });
114
118
  }
115
119
  });
116
120
  }
117
121
  get user() {
118
- return (req, res) => __awaiter(this, void 0, void 0, function* () {
119
- const tokenSet = yield __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getFreshTokenSet).call(this, req);
120
- if (!tokenSet) {
121
- return res.sendStatus(401);
122
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
123
+ var _a;
124
+ try {
125
+ const tokenSet = yield __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getFreshTokenSet).call(this, req);
126
+ if (!tokenSet) {
127
+ return res.sendStatus(401);
128
+ }
129
+ let claims = tokenSet.claims();
130
+ if (__classPrivateFieldGet(this, _OidcMiddleware_config, "f").userClaims) {
131
+ claims = __classPrivateFieldGet(this, _OidcMiddleware_config, "f").userClaims.reduce((acc, claim) => {
132
+ acc[claim] = claims[claim];
133
+ return acc;
134
+ }, {});
135
+ }
136
+ return res.send(claims);
122
137
  }
123
- let claims = tokenSet.claims();
124
- if (__classPrivateFieldGet(this, _OidcMiddleware_config, "f").userClaims) {
125
- claims = __classPrivateFieldGet(this, _OidcMiddleware_config, "f").userClaims.reduce((acc, claim) => {
126
- acc[claim] = claims[claim];
127
- return acc;
128
- }, {});
138
+ catch (e) {
139
+ console.error(`Error in /user sid=${redact((_a = req.session) === null || _a === void 0 ? void 0 : _a.id)}`, e);
140
+ next(e);
129
141
  }
130
- return res.send(claims);
131
142
  });
132
143
  }
133
144
  get logout() {
134
145
  return (req, res) => {
135
146
  const tokenSet = req.session.tokenSet && new TokenSet(req.session.tokenSet);
136
- //TODO: støtt frontchannel SLO
137
147
  req.session.destroy(() => {
138
148
  res.redirect(__classPrivateFieldGet(this, _OidcMiddleware_clientManager, "f").client.endSessionUrl({
139
149
  id_token_hint: tokenSet === null || tokenSet === void 0 ? void 0 : tokenSet.id_token,
@@ -141,6 +151,22 @@ export class OidcMiddleware {
141
151
  });
142
152
  };
143
153
  }
154
+ get frontChannelLogout() {
155
+ return (req, res) => __awaiter(this, void 0, void 0, function* () {
156
+ var _a;
157
+ const { iss, sid } = req.query;
158
+ console.log(`Front channel logout: params iss=${iss}, sid=${redact(sid)}`);
159
+ if (sid) {
160
+ try {
161
+ yield ((_a = req.destroySessionByIdTokenSid) === null || _a === void 0 ? void 0 : _a.call(req, sid));
162
+ }
163
+ catch (e) {
164
+ console.error("Failed to destroy session", e);
165
+ }
166
+ }
167
+ res.sendStatus(200);
168
+ });
169
+ }
144
170
  }
145
171
  _OidcMiddleware_clientManager = new WeakMap(), _OidcMiddleware_config = new WeakMap(), _OidcMiddleware_refreshPromises = new WeakMap(), _OidcMiddleware_instances = new WeakSet(), _OidcMiddleware_refreshTokenSet = function _OidcMiddleware_refreshTokenSet(req, tokenSet) {
146
172
  return __awaiter(this, void 0, void 0, function* () {
@@ -149,35 +175,41 @@ _OidcMiddleware_clientManager = new WeakMap(), _OidcMiddleware_config = new Weak
149
175
  const sessionId = req.session.id;
150
176
  const refreshToken = tokenSet.refresh_token;
151
177
  const doRefresh = () => __awaiter(this, void 0, void 0, function* () {
152
- console.log(`Token refresh starting. sid=${sessionId}`);
178
+ console.log(`Token refresh starting. sid=${redact(sessionId)}`);
153
179
  try {
154
180
  const refreshedTokenSet = yield __classPrivateFieldGet(this, _OidcMiddleware_clientManager, "f").client.refresh(refreshToken);
155
- console.log(`Token refresh OK. sid=${sessionId}`);
181
+ console.log(`Token refresh OK. sid=${redact(sessionId)}`);
156
182
  return refreshedTokenSet;
157
183
  }
158
184
  catch (err) {
159
- console.log(`Token refresh failed. sid=${sessionId}`, err);
185
+ console.log(`Token refresh failed. sid=${redact(sessionId)}`, err);
160
186
  return null;
161
187
  }
162
188
  });
163
189
  const refreshPromise = (_a = (_b = __classPrivateFieldGet(this, _OidcMiddleware_refreshPromises, "f"))[refreshToken]) !== null && _a !== void 0 ? _a : (_b[refreshToken] = doRefresh().finally(() => {
164
- console.log(`Token refresh finished. Cleaning up. sid=${sessionId}`);
190
+ console.log(`Token refresh finished. Cleaning up. sid=${redact(sessionId)}`);
165
191
  setTimeout(() => {
166
192
  delete __classPrivateFieldGet(this, _OidcMiddleware_refreshPromises, "f")[refreshToken];
167
193
  }, 10000);
168
194
  }));
169
195
  const refreshedTokenSet = yield refreshPromise;
170
- req.session.tokenSet = refreshedTokenSet;
171
- return refreshedTokenSet;
196
+ if (refreshedTokenSet) {
197
+ Object.assign(req.session.tokenSet, refreshedTokenSet);
198
+ }
199
+ else {
200
+ req.session.tokenSet = null;
201
+ }
202
+ return req.session.tokenSet;
172
203
  });
173
204
  }, _OidcMiddleware_getFreshTokenSet = function _OidcMiddleware_getFreshTokenSet(req) {
174
205
  return __awaiter(this, void 0, void 0, function* () {
175
206
  const tokenSet = req.session.tokenSet && new TokenSet(req.session.tokenSet);
176
207
  if (!tokenSet) {
177
- console.log("No tokenSet found in session");
208
+ console.log(`No tokenSet found in session sid=${redact(req.session.id)}`);
178
209
  return;
179
210
  }
180
211
  if (tokenSet.expired()) {
212
+ console.log(`TokenSet expired sid=${redact(req.session.id)}`);
181
213
  const newTokenSet = yield __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_refreshTokenSet).call(this, req, tokenSet);
182
214
  return newTokenSet && new TokenSet(newTokenSet);
183
215
  }
@@ -1,2 +1,2 @@
1
- export function sessions(config: any): any;
1
+ export function sessions(config: any): any[];
2
2
  //# sourceMappingURL=sessions.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.d.mts","sourceRoot":"","sources":["../../src/middleware/sessions.mjs"],"names":[],"mappings":"AAQA,2CAyBC"}
1
+ {"version":3,"file":"sessions.d.mts","sourceRoot":"","sources":["../../src/middleware/sessions.mjs"],"names":[],"mappings":"AAiDA,6CAgCC"}
@@ -1,17 +1,63 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import session from "express-session";
2
11
  import dynamoDbStore from "connect-dynamodb";
12
+ import { DeleteItemCommand, DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
13
+ import { redact } from "../utils.js";
3
14
  function dynamoDbSessionStore(config = {}) {
15
+ const client = new DynamoDBClient({});
4
16
  const DynamoDbStore = dynamoDbStore({ session });
5
- return new DynamoDbStore(config);
17
+ const sessionStoreConfig = Object.assign(Object.assign({}, config), { client, specialKeys: [
18
+ { name: "idp-sid", type: "S" }
19
+ ], skipThrowMissingSpecialKeys: true });
20
+ const sessionStore = new DynamoDbStore(sessionStoreConfig);
21
+ sessionStore.destroyByIdTokenSid = (idTokenSid) => __awaiter(this, void 0, void 0, function* () {
22
+ console.log(`Front channel logout: deleting session(s) with idp-sid=${redact(idTokenSid)}`);
23
+ const query = new QueryCommand({
24
+ TableName: config.table,
25
+ IndexName: "idp-sid-index",
26
+ ExpressionAttributeValues: { ":sid": { S: idTokenSid } },
27
+ ExpressionAttributeNames: { "#k": "idp-sid" },
28
+ KeyConditionExpression: "#k = :sid",
29
+ ProjectionExpression: "id"
30
+ });
31
+ const res = yield client.send(query);
32
+ yield Promise.all(res.Items.map((item) => {
33
+ var _a;
34
+ console.log(`Front channel logout: deleting session ${redact((_a = item.id) === null || _a === void 0 ? void 0 : _a.S, 10)}`);
35
+ return client.send(new DeleteItemCommand({
36
+ TableName: config.table,
37
+ Key: { id: item.id }
38
+ }));
39
+ }));
40
+ console.log(`Front channel logout: completed. ${res.Count} session(s) deleted`);
41
+ });
42
+ return sessionStore;
43
+ }
44
+ function memorySessionStore(config = {}) {
45
+ const sessionStore = new session.MemoryStore(config);
46
+ sessionStore.destroyByIdTokenSid = (idTokenSid) => {
47
+ // dummy.
48
+ console.log(`Pretending to destroyByIdTokenSid. idp-sid=${redact(idTokenSid)}`);
49
+ };
50
+ return sessionStore;
6
51
  }
7
52
  export function sessions(config) {
8
- var _a;
53
+ var _a, _b;
9
54
  let sessionStore;
10
55
  if (config.sessionStoreType === 'memory') {
11
- sessionStore = undefined;
56
+ const sessionStoreOptions = (_a = config.sessionStoreOptions) !== null && _a !== void 0 ? _a : {};
57
+ sessionStore = memorySessionStore(sessionStoreOptions);
12
58
  }
13
59
  else if (config.sessionStoreType === 'dynamodb') {
14
- const sessionStoreOptions = (_a = config.sessionStoreOptions) !== null && _a !== void 0 ? _a : {};
60
+ const sessionStoreOptions = (_b = config.sessionStoreOptions) !== null && _b !== void 0 ? _b : {};
15
61
  sessionStore = dynamoDbSessionStore(sessionStoreOptions);
16
62
  }
17
63
  else if (config.sessionStoreType) {
@@ -20,16 +66,22 @@ export function sessions(config) {
20
66
  else {
21
67
  throw Error('missing sessionStoreType');
22
68
  }
23
- return session({
24
- secret: config.sessionSecret,
25
- store: sessionStore,
26
- resave: false,
27
- saveUninitialized: false,
28
- cookie: config.cookie || {
29
- httpOnly: true,
30
- path: config.cookiePath,
31
- secure: config.cookieSecure,
32
- sameSite: config.cookieSameSite
33
- },
34
- });
69
+ return [
70
+ session({
71
+ secret: config.sessionSecret,
72
+ store: sessionStore,
73
+ resave: false,
74
+ saveUninitialized: false,
75
+ cookie: config.cookie || {
76
+ httpOnly: true,
77
+ path: config.cookiePath,
78
+ secure: config.cookieSecure,
79
+ sameSite: config.cookieSameSite
80
+ },
81
+ }),
82
+ (req) => {
83
+ // make this function available to request handlers
84
+ req.destroySessionByIdTokenSid = sessionStore === null || sessionStore === void 0 ? void 0 : sessionStore.destroyByIdTokenSid;
85
+ }
86
+ ];
35
87
  }
@@ -2,7 +2,7 @@ export type User = {
2
2
  [k: string]: string | number;
3
3
  };
4
4
  export type AuthContextProps = {
5
- state: 'pending' | 'authenticated' | 'unauthenticated' | 'expired';
5
+ state: 'pending' | 'authenticated' | 'unauthenticated' | 'expired' | 'error';
6
6
  user?: User;
7
7
  login: () => void;
8
8
  logout: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/react/AuthContext.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,CAAC;IACnE,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,WAAW,2CAAyD,CAAA"}
1
+ {"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/react/AuthContext.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,WAAW,2CAAyD,CAAA"}
@@ -26,17 +26,14 @@ export function AuthContextProvider({ children, authRequired = false, loaderComp
26
26
  return __awaiter(this, void 0, void 0, function* () {
27
27
  const res = yield fetch(`${baseUrl}/auth/user`);
28
28
  if (res.ok) {
29
- try {
30
- return yield res.json();
31
- }
32
- catch (e) {
33
- console.error('failed to parse user', e);
34
- return null;
35
- }
29
+ return yield res.json();
36
30
  }
37
- else {
31
+ else if (res.status === 401) {
38
32
  return null;
39
33
  }
34
+ else {
35
+ throw Error(`Failed to get user: ${res.status} ${res.statusText}`);
36
+ }
40
37
  });
41
38
  }
42
39
  function startPoller() {
@@ -78,6 +75,9 @@ export function AuthContextProvider({ children, authRequired = false, loaderComp
78
75
  else {
79
76
  setState('unauthenticated');
80
77
  }
78
+ }).catch((err) => {
79
+ console.error(err);
80
+ setState('error');
81
81
  }));
82
82
  }, []);
83
83
  useEffect(() => {
@@ -0,0 +1,2 @@
1
+ export function redact(string: any, length?: number): string;
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAAA,6DAEC"}
package/dist/utils.js ADDED
@@ -0,0 +1,3 @@
1
+ export function redact(string, length = 5) {
2
+ return (string === null || string === void 0 ? void 0 : string.substring(0, length)) + '***';
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/auth-bff",
3
- "version": "1.5.0",
3
+ "version": "1.6.0-beta1",
4
4
  "repository": "https://github.com/oslokommune/auth-bff.git",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -8,7 +8,8 @@
8
8
  "scripts": {
9
9
  "build": "tsc",
10
10
  "run": "node ./dist/server.mjs",
11
- "build-and-publish": "tsc && npm publish"
11
+ "build-and-publish": "tsc && npm publish",
12
+ "build-and-publish-prerelease": "tsc && npm publish --tag prerelease"
12
13
  },
13
14
  "exports": {
14
15
  "./vite-plugin": "./dist/vite-plugin.mjs",