@vunexa/lixa 0.1.6-alpha.2 → 0.1.6-alpha.4

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/dist/index.cjs CHANGED
@@ -217,6 +217,7 @@ var Lixa = class _Lixa {
217
217
  config;
218
218
  stateHandler;
219
219
  sessionHandler;
220
+ resourceHandler;
220
221
  debug;
221
222
  /**
222
223
  * Creates a new Lixa instance with the provided configuration.
@@ -235,6 +236,7 @@ var Lixa = class _Lixa {
235
236
  this.config = config;
236
237
  this.stateHandler = config.stateHandler || _Lixa.LOCAL_STATE_HANDLER;
237
238
  this.sessionHandler = config.sessionHandler || _Lixa.LOCAL_SESSION_HANDLER;
239
+ this.resourceHandler = config.resourceHandler || _Lixa.LOCAL_RESOURCE_HANDLER;
238
240
  this.debug = config.debug || false;
239
241
  this.log("INFO", "Init", "Initializing Lixa instance", {
240
242
  providers: Object.keys(config.providers),
@@ -914,6 +916,45 @@ var Lixa = class _Lixa {
914
916
  * @param params - Object containing sessionId, provider, code, state, and requested scopes
915
917
  * @returns Updated Session containing stored resource tokens under session.resources[provider]
916
918
  */
919
+ static userResourceStore = /* @__PURE__ */ new Map();
920
+ static LOCAL_RESOURCE_HANDLER = {
921
+ resourceStorage: {
922
+ saveResource: async (userId, provider, resource) => {
923
+ let userMap = _Lixa.userResourceStore.get(userId);
924
+ if (!userMap) {
925
+ userMap = /* @__PURE__ */ new Map();
926
+ _Lixa.userResourceStore.set(userId, userMap);
927
+ }
928
+ userMap.set(provider.toLowerCase(), resource);
929
+ },
930
+ getResource: async (userId, provider) => {
931
+ const userMap = _Lixa.userResourceStore.get(userId);
932
+ return userMap?.get(provider.toLowerCase()) || null;
933
+ },
934
+ getUserResources: async (userId) => {
935
+ const userMap = _Lixa.userResourceStore.get(userId);
936
+ const result = {};
937
+ if (userMap) {
938
+ for (const [p, r] of userMap.entries()) {
939
+ result[p] = r;
940
+ }
941
+ }
942
+ return result;
943
+ },
944
+ deleteResource: async (userId, provider) => {
945
+ const userMap = _Lixa.userResourceStore.get(userId);
946
+ if (userMap) {
947
+ userMap.delete(provider.toLowerCase());
948
+ }
949
+ }
950
+ }
951
+ };
952
+ getUserKeyFromSession(session) {
953
+ return session.userId || session.email || session.id || "anonymous";
954
+ }
955
+ /**
956
+ * Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
957
+ */
917
958
  async handleResourceCallback(params) {
918
959
  const { sessionId, provider, code, state, scopes } = params;
919
960
  const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
@@ -937,44 +978,176 @@ var Lixa = class _Lixa {
937
978
  }
938
979
  }
939
980
  const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
940
- activeSession.resources = activeSession.resources || {};
941
- activeSession.resources[providerType] = {
981
+ const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
982
+ const connectedResource = {
942
983
  provider: providerType,
943
984
  accessToken: tokens.access_token,
944
985
  refreshToken: tokens.refresh_token,
986
+ expiresAt,
945
987
  scopes: scopes || (tokens.scope ? tokens.scope.split(" ") : []),
946
988
  raw: tokens,
947
989
  connectedAt: Date.now()
948
990
  };
991
+ const userKey = this.getUserKeyFromSession(activeSession);
992
+ const resourceStorage = this.resourceHandler.resourceStorage || _Lixa.LOCAL_RESOURCE_HANDLER.resourceStorage;
993
+ await resourceStorage.saveResource(userKey, providerType, connectedResource);
994
+ activeSession.resources = activeSession.resources || {};
995
+ activeSession.resources[providerType] = connectedResource;
949
996
  await sessionStorage.saveSession(sessionId, activeSession, 86400);
950
997
  return activeSession;
951
998
  }
952
999
  /**
953
- * Retrieves a connected resource provider token for an active session.
1000
+ * Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
1001
+ * Automatically refreshes expired access tokens if a refresh token is present.
1002
+ *
1003
+ * @param userIdOrEmail - User identifier or email
1004
+ * @param provider - Resource provider identifier (e.g. 'github', 'google')
1005
+ */
1006
+ async getUserResource(userIdOrEmail, provider) {
1007
+ const resourceStorage = this.resourceHandler.resourceStorage || _Lixa.LOCAL_RESOURCE_HANDLER.resourceStorage;
1008
+ const providerType = provider.toLowerCase();
1009
+ const resource = await resourceStorage.getResource(userIdOrEmail, providerType);
1010
+ if (!resource) return null;
1011
+ if (resource.refreshToken && resource.expiresAt && Date.now() >= resource.expiresAt - 6e4) {
1012
+ this.log("INFO", "Token", `Resource access token for user '${userIdOrEmail}' on '${providerType}' is expired. Auto-refreshing...`);
1013
+ try {
1014
+ return await this.refreshUserResourceToken(userIdOrEmail, providerType);
1015
+ } catch (error) {
1016
+ this.log("ERROR", "Token", `Failed to auto-refresh resource token for user '${userIdOrEmail}' on '${providerType}'`, { error: String(error) });
1017
+ }
1018
+ }
1019
+ return resource;
1020
+ }
1021
+ /**
1022
+ * Retrieves all connected resources for a specific User ID / Email.
1023
+ */
1024
+ async getUserResources(userIdOrEmail) {
1025
+ const resourceStorage = this.resourceHandler.resourceStorage || _Lixa.LOCAL_RESOURCE_HANDLER.resourceStorage;
1026
+ return await resourceStorage.getUserResources(userIdOrEmail);
1027
+ }
1028
+ /**
1029
+ * Retrieves a connected resource provider token for an active session, auto-refreshing expired tokens if possible.
954
1030
  *
955
1031
  * @param sessionId - Active session ID
956
- * @param provider - Provider identifier (e.g. 'github')
1032
+ * @param provider - Provider identifier (e.g. 'github', 'google')
957
1033
  */
958
1034
  async getConnectedResource(sessionId, provider) {
959
1035
  const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
960
1036
  const activeSession = await sessionStorage.getSession(sessionId);
961
- if (!activeSession || !activeSession.resources) return null;
962
- return activeSession.resources[provider.toLowerCase()] || null;
1037
+ if (!activeSession) return null;
1038
+ const providerType = provider.toLowerCase();
1039
+ const userKey = this.getUserKeyFromSession(activeSession);
1040
+ let resource = await this.getUserResource(userKey, providerType);
1041
+ if (!resource && activeSession.resources) {
1042
+ resource = activeSession.resources[providerType] || null;
1043
+ }
1044
+ if (resource) {
1045
+ if (resource.refreshToken && resource.expiresAt && Date.now() >= resource.expiresAt - 6e4) {
1046
+ this.log("INFO", "Token", `Resource access token for user '${userKey}' on '${providerType}' is expired. Auto-refreshing...`);
1047
+ try {
1048
+ resource = await this.refreshResourceToken(sessionId, providerType);
1049
+ } catch (error) {
1050
+ this.log("ERROR", "Token", `Failed to auto-refresh resource token for user '${userKey}' on '${providerType}'`, { error: String(error) });
1051
+ }
1052
+ }
1053
+ activeSession.resources = activeSession.resources || {};
1054
+ activeSession.resources[providerType] = resource;
1055
+ }
1056
+ return resource;
963
1057
  }
964
1058
  /**
965
- * Disconnects a resource provider from an active session.
1059
+ * Refreshes a user's resource access token using its refresh token.
966
1060
  *
967
- * @param sessionId - Active session ID
968
- * @param provider - Provider identifier to disconnect
1061
+ * @param userIdOrEmail - User identifier or email
1062
+ * @param provider - Provider identifier (e.g. 'google', 'github')
969
1063
  */
970
- async disconnectResource(sessionId, provider) {
1064
+ async refreshUserResourceToken(userIdOrEmail, provider, existingResource) {
1065
+ const resourceStorage = this.resourceHandler.resourceStorage || _Lixa.LOCAL_RESOURCE_HANDLER.resourceStorage;
1066
+ const providerType = provider.toLowerCase();
1067
+ let resource = existingResource || await resourceStorage.getResource(userIdOrEmail, providerType);
1068
+ if (!resource || !resource.refreshToken) {
1069
+ throw new Error(`No refresh token available for user '${userIdOrEmail}' on connected resource '${provider}'`);
1070
+ }
1071
+ const providerConfig = this.findProviderByType(providerType);
1072
+ if (!providerConfig) {
1073
+ throw new Error(`Provider '${provider}' is not configured`);
1074
+ }
1075
+ const providerImpl = this.getProvider(providerType, providerConfig);
1076
+ const body = {
1077
+ client_id: providerConfig.clientId,
1078
+ client_secret: providerConfig.clientSecret,
1079
+ grant_type: "refresh_token",
1080
+ refresh_token: resource.refreshToken
1081
+ };
1082
+ const response = await fetch(providerImpl.tokenEndpoint, {
1083
+ method: "POST",
1084
+ headers: {
1085
+ "Content-Type": "application/x-www-form-urlencoded",
1086
+ Accept: "application/json"
1087
+ },
1088
+ body: new URLSearchParams(body).toString()
1089
+ });
1090
+ if (!response.ok) {
1091
+ const errorText = await response.text();
1092
+ throw new Error(`Failed to refresh resource token for user '${userIdOrEmail}' on '${provider}': ${response.status} - ${errorText}`);
1093
+ }
1094
+ const tokens = await response.json();
1095
+ const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
1096
+ resource.accessToken = tokens.access_token;
1097
+ if (tokens.refresh_token) {
1098
+ resource.refreshToken = tokens.refresh_token;
1099
+ }
1100
+ if (expiresAt) {
1101
+ resource.expiresAt = expiresAt;
1102
+ }
1103
+ resource.raw = tokens;
1104
+ resource.connectedAt = Date.now();
1105
+ await resourceStorage.saveResource(userIdOrEmail, providerType, resource);
1106
+ return resource;
1107
+ }
1108
+ /**
1109
+ * Refreshes a connected resource access token for an active session.
1110
+ */
1111
+ async refreshResourceToken(sessionId, provider) {
971
1112
  const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
972
1113
  const activeSession = await sessionStorage.getSession(sessionId);
973
- if (!activeSession || !activeSession.resources) return false;
974
- delete activeSession.resources[provider.toLowerCase()];
1114
+ if (!activeSession) {
1115
+ throw new Error("Active session not found.");
1116
+ }
1117
+ const providerType = provider.toLowerCase();
1118
+ const userKey = this.getUserKeyFromSession(activeSession);
1119
+ const existingResource = activeSession.resources ? activeSession.resources[providerType] : void 0;
1120
+ const refreshed = await this.refreshUserResourceToken(userKey, providerType, existingResource);
1121
+ activeSession.resources = activeSession.resources || {};
1122
+ activeSession.resources[providerType] = refreshed;
975
1123
  await sessionStorage.saveSession(sessionId, activeSession, 86400);
1124
+ return refreshed;
1125
+ }
1126
+ /**
1127
+ * Disconnects a resource provider for a specific User ID / Email.
1128
+ */
1129
+ async disconnectUserResource(userIdOrEmail, provider) {
1130
+ const resourceStorage = this.resourceHandler.resourceStorage || _Lixa.LOCAL_RESOURCE_HANDLER.resourceStorage;
1131
+ await resourceStorage.deleteResource(userIdOrEmail, provider.toLowerCase());
976
1132
  return true;
977
1133
  }
1134
+ /**
1135
+ * Disconnects a resource provider from an active session and user account.
1136
+ */
1137
+ async disconnectResource(sessionId, provider) {
1138
+ const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
1139
+ const activeSession = await sessionStorage.getSession(sessionId);
1140
+ if (activeSession) {
1141
+ const userKey = this.getUserKeyFromSession(activeSession);
1142
+ await this.disconnectUserResource(userKey, provider);
1143
+ if (activeSession.resources) {
1144
+ delete activeSession.resources[provider.toLowerCase()];
1145
+ await sessionStorage.saveSession(sessionId, activeSession, 86400);
1146
+ }
1147
+ return true;
1148
+ }
1149
+ return false;
1150
+ }
978
1151
  async fetchSessionInfo(sessionId) {
979
1152
  const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
980
1153
  return await sessionStorage.getSession(sessionId);