@dloizides/auth-client 2.1.0 → 3.2.1
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.
- package/CHANGELOG.md +67 -0
- package/README.md +37 -1
- package/dist/{AuthClient-D95OMajD.d.ts → AuthClient-Cv7btBX0.d.ts} +1 -1
- package/dist/{AuthClient-BGr8L03W.d.mts → AuthClient-D8Ul-aGa.d.mts} +1 -1
- package/dist/index.d.mts +181 -5
- package/dist/index.d.ts +181 -5
- package/dist/index.js +174 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/package.json +124 -124
package/dist/index.mjs
CHANGED
|
@@ -1057,6 +1057,179 @@ var AuthApiClient = class {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
};
|
|
1059
1059
|
|
|
1060
|
+
// src/bff/BffAuthClient.ts
|
|
1061
|
+
var CSRF_HEADER = "X-BFF-Csrf";
|
|
1062
|
+
var CSRF_HEADER_VALUE = "1";
|
|
1063
|
+
var JSON_CONTENT_TYPE = "application/json";
|
|
1064
|
+
var ENDPOINTS = {
|
|
1065
|
+
login: "/bff/login",
|
|
1066
|
+
logout: "/bff/logout",
|
|
1067
|
+
me: "/bff/me",
|
|
1068
|
+
register: "/bff/register",
|
|
1069
|
+
forgotPassword: "/bff/forgot-password",
|
|
1070
|
+
resetPassword: "/bff/reset-password",
|
|
1071
|
+
otpRequest: "/bff/otp/request",
|
|
1072
|
+
otpVerify: "/bff/otp/verify",
|
|
1073
|
+
pinLogin: "/bff/pin/login"
|
|
1074
|
+
};
|
|
1075
|
+
function isRecord(value) {
|
|
1076
|
+
return typeof value === "object" && value !== null;
|
|
1077
|
+
}
|
|
1078
|
+
function extractUser(data) {
|
|
1079
|
+
if (!isRecord(data)) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const envelope = data;
|
|
1083
|
+
return isRecord(envelope.user) ? envelope.user : null;
|
|
1084
|
+
}
|
|
1085
|
+
function toOtpRequestResult(data) {
|
|
1086
|
+
if (!isRecord(data)) {
|
|
1087
|
+
return { success: true, expiresIn: 0, code: null };
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
success: typeof data.success === "boolean" ? data.success : true,
|
|
1091
|
+
expiresIn: typeof data.expiresIn === "number" ? data.expiresIn : 0,
|
|
1092
|
+
code: typeof data.code === "string" ? data.code : null
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
var BffAuthClient = class {
|
|
1096
|
+
constructor(options) {
|
|
1097
|
+
this.http = options.http;
|
|
1098
|
+
this.baseUrl = (options.baseUrl ?? "").replace(/\/$/, "");
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores
|
|
1102
|
+
* the tokens in its Redis vault, and sets the httpOnly session cookie.
|
|
1103
|
+
* Returns the sanitised user. Throws on a non-2xx response.
|
|
1104
|
+
*/
|
|
1105
|
+
async login(request) {
|
|
1106
|
+
const data = await this.postState(ENDPOINTS.login, request, "login");
|
|
1107
|
+
const user = extractUser(data);
|
|
1108
|
+
if (user === null) {
|
|
1109
|
+
throw new Error("login: BFF response missing user");
|
|
1110
|
+
}
|
|
1111
|
+
return user;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis
|
|
1115
|
+
* session, and clears the cookie. Non-fatal: a failed logout still leaves
|
|
1116
|
+
* the SPA logged out client-side. Throws only on a non-2xx response.
|
|
1117
|
+
*/
|
|
1118
|
+
async logout() {
|
|
1119
|
+
await this.postState(ENDPOINTS.logout, void 0, "logout");
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* `GET /bff/me` — the live session's sanitised user, or `null` when there is
|
|
1123
|
+
* no session (the BFF answers `401`). Used at app load to bootstrap auth
|
|
1124
|
+
* state in place of the old token-in-storage check.
|
|
1125
|
+
*/
|
|
1126
|
+
async getCurrentUser() {
|
|
1127
|
+
const response = await this.http({
|
|
1128
|
+
url: `${this.baseUrl}${ENDPOINTS.me}`,
|
|
1129
|
+
method: "GET",
|
|
1130
|
+
headers: { Accept: JSON_CONTENT_TYPE },
|
|
1131
|
+
credentials: "include"
|
|
1132
|
+
});
|
|
1133
|
+
if (!response.ok) {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
return extractUser(response.data);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* `POST /bff/register` — the BFF proxies registration to TenantService and,
|
|
1140
|
+
* on success, establishes a session exactly like `login`. Returns the user.
|
|
1141
|
+
*/
|
|
1142
|
+
async register(request) {
|
|
1143
|
+
const data = await this.postState(ENDPOINTS.register, request, "register");
|
|
1144
|
+
const user = extractUser(data);
|
|
1145
|
+
if (user === null) {
|
|
1146
|
+
throw new Error("register: BFF response missing user");
|
|
1147
|
+
}
|
|
1148
|
+
return user;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* `POST /bff/forgot-password` — proxied to TenantService. The backend
|
|
1152
|
+
* returns 200 unconditionally (no email enumeration); anything else throws.
|
|
1153
|
+
*/
|
|
1154
|
+
async forgotPassword(request) {
|
|
1155
|
+
await this.postState(ENDPOINTS.forgotPassword, request, "forgot-password");
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx
|
|
1159
|
+
* response (e.g. `400` for an invalid / expired token).
|
|
1160
|
+
*/
|
|
1161
|
+
async resetPassword(request) {
|
|
1162
|
+
await this.postState(ENDPOINTS.resetPassword, request, "reset-password");
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* `POST /bff/otp/request` — the BFF proxies to TenantService, which generates
|
|
1166
|
+
* a short-TTL code and emails it.
|
|
1167
|
+
*
|
|
1168
|
+
* The endpoint is anti-enumeration: a `200` is the normal path whether or not
|
|
1169
|
+
* the identifier is registered. This method therefore **returns** the relayed
|
|
1170
|
+
* `{ success, expiresIn, code }` body (so the UI can show the expiry) rather
|
|
1171
|
+
* than treating a 200 as opaque. It still throws on a non-2xx — a `501`
|
|
1172
|
+
* (OTP not enabled) or `502` (upstream down) is a real failure to surface.
|
|
1173
|
+
*/
|
|
1174
|
+
async requestOtp(request) {
|
|
1175
|
+
const data = await this.postState(ENDPOINTS.otpRequest, request, "otp-request");
|
|
1176
|
+
return toOtpRequestResult(data);
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* `POST /bff/otp/verify` — the BFF runs the OTP direct-grant against Keycloak
|
|
1180
|
+
* server-side, stores the tokens in its Redis vault, and sets the httpOnly
|
|
1181
|
+
* session cookie. Returns the sanitised user, exactly like `login`. Throws on
|
|
1182
|
+
* a non-2xx (e.g. `401` for a bad / expired code).
|
|
1183
|
+
*/
|
|
1184
|
+
async verifyOtp(request) {
|
|
1185
|
+
const data = await this.postState(ENDPOINTS.otpVerify, request, "otp-verify");
|
|
1186
|
+
const user = extractUser(data);
|
|
1187
|
+
if (user === null) {
|
|
1188
|
+
throw new Error("otp-verify: BFF response missing user");
|
|
1189
|
+
}
|
|
1190
|
+
return user;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* `POST /bff/pin/login` — the BFF runs the event-scoped PIN direct-grant
|
|
1194
|
+
* against Keycloak server-side (the `(event, pin)` pair resolves to the
|
|
1195
|
+
* staff member's KC account + event-scoped role), stores the tokens in its
|
|
1196
|
+
* Redis vault, and sets the httpOnly session cookie. Returns the sanitised
|
|
1197
|
+
* user, exactly like `login` / `verifyOtp`. Throws on a non-2xx — `401` for
|
|
1198
|
+
* a bad / expired / locked-out PIN or an unknown event, `501` when PIN login
|
|
1199
|
+
* is not an enabled method for this BFF.
|
|
1200
|
+
*/
|
|
1201
|
+
async pinLogin(request) {
|
|
1202
|
+
const data = await this.postState(ENDPOINTS.pinLogin, request, "pin-login");
|
|
1203
|
+
const user = extractUser(data);
|
|
1204
|
+
if (user === null) {
|
|
1205
|
+
throw new Error("pin-login: BFF response missing user");
|
|
1206
|
+
}
|
|
1207
|
+
return user;
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Shared POST for every state-changing `/bff/*` call: same-origin, cookie
|
|
1211
|
+
* included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.
|
|
1212
|
+
*/
|
|
1213
|
+
async postState(path, body, label) {
|
|
1214
|
+
const headers = {
|
|
1215
|
+
"Content-Type": JSON_CONTENT_TYPE,
|
|
1216
|
+
Accept: JSON_CONTENT_TYPE,
|
|
1217
|
+
[CSRF_HEADER]: CSRF_HEADER_VALUE
|
|
1218
|
+
};
|
|
1219
|
+
const response = await this.http({
|
|
1220
|
+
url: `${this.baseUrl}${path}`,
|
|
1221
|
+
method: "POST",
|
|
1222
|
+
headers,
|
|
1223
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
1224
|
+
credentials: "include"
|
|
1225
|
+
});
|
|
1226
|
+
if (!response.ok) {
|
|
1227
|
+
throw new Error(`${label} failed with status ${String(response.status)}`);
|
|
1228
|
+
}
|
|
1229
|
+
return response.data;
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1060
1233
|
// src/utils/normalizeKeycloakUser.ts
|
|
1061
1234
|
function isNonEmptyString(value) {
|
|
1062
1235
|
return typeof value === "string" && value !== "";
|
|
@@ -1167,6 +1340,6 @@ function decodeUtf8(binary) {
|
|
|
1167
1340
|
return new TextDecoder("utf-8").decode(bytes);
|
|
1168
1341
|
}
|
|
1169
1342
|
|
|
1170
|
-
export { AuthApiClient, AuthClient, AuthEventEmitter, BiometricGate, BrowserStorageTokenStorage, CookieTokenStorage, InMemoryTokenStorage, InactivityTracker, KeycloakRoles, RefreshInterceptor, SecureStoreTokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, clearDiscoveryCache, computeExpiresAt, createFetchHttpClient, decodeJwt, deriveCodeChallenge, exchangeAuthorizationCode, extractAuthCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, refreshAccessToken, tokenResponseToAuthTokens };
|
|
1343
|
+
export { AuthApiClient, AuthClient, AuthEventEmitter, BffAuthClient, BiometricGate, BrowserStorageTokenStorage, CookieTokenStorage, InMemoryTokenStorage, InactivityTracker, KeycloakRoles, RefreshInterceptor, SecureStoreTokenStorage, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, clearDiscoveryCache, computeExpiresAt, createFetchHttpClient, decodeJwt, deriveCodeChallenge, exchangeAuthorizationCode, extractAuthCode, fetchDiscoveryDocument, generateCodeVerifier, generatePkcePair, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, refreshAccessToken, tokenResponseToAuthTokens };
|
|
1171
1344
|
//# sourceMappingURL=index.mjs.map
|
|
1172
1345
|
//# sourceMappingURL=index.mjs.map
|