@rixl/sdk 0.32.0 → 0.33.0

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.js CHANGED
@@ -2576,6 +2576,7 @@ const videosV1VideoServiceGetVideo = (options) => (options.client ?? client).get
2576
2576
  const GLOBAL_PREFIX = "__rixl_auth";
2577
2577
  /** OAuth state storage key prefix */
2578
2578
  const STATE_STORAGE_KEY_PREFIX = "__rixl_auth_state_";
2579
+ const PKCE_STORAGE_KEY_PREFIX = "__rixl_auth_pkce_";
2579
2580
  /** Social connect storage key prefix */
2580
2581
  const SOCIAL_CONNECT_KEY_PREFIX = "__rixl_auth_social_connect_";
2581
2582
  /** HTTP Status codes - only codes actually used in the library */
@@ -2592,7 +2593,7 @@ const HTTP_STATUS = {
2592
2593
 
2593
2594
  //#endregion
2594
2595
  //#region src/auth/state.ts
2595
- const storagePath = (provider) => STATE_STORAGE_KEY_PREFIX + provider;
2596
+ const storagePath$1 = (provider) => STATE_STORAGE_KEY_PREFIX + provider;
2596
2597
  /**
2597
2598
  * Retrieves and validates the OAuth state from session storage
2598
2599
  * @param provider The provider identifier
@@ -2600,7 +2601,7 @@ const storagePath = (provider) => STATE_STORAGE_KEY_PREFIX + provider;
2600
2601
  * @returns True if the state is valid, false otherwise
2601
2602
  */
2602
2603
  function validateOAuthState(provider, state) {
2603
- return sessionStorage.getItem(storagePath(provider)) === state;
2604
+ return sessionStorage.getItem(storagePath$1(provider)) === state;
2604
2605
  }
2605
2606
  /**
2606
2607
  * Discards the stored state for a provider so the next login round-trip mints a
@@ -2609,7 +2610,7 @@ function validateOAuthState(provider, state) {
2609
2610
  * @param provider The provider identifier
2610
2611
  */
2611
2612
  function clearOauthState(provider) {
2612
- sessionStorage.removeItem(storagePath(provider));
2613
+ sessionStorage.removeItem(storagePath$1(provider));
2613
2614
  }
2614
2615
  /**
2615
2616
  * Gets or Generates a complete state parameter for OAuth requests
@@ -2617,11 +2618,11 @@ function clearOauthState(provider) {
2617
2618
  * @returns A state string containing the provider identifier and random data
2618
2619
  */
2619
2620
  function getOauthState(provider) {
2620
- return sessionStorage.getItem(storagePath(provider)) ?? generateOauthState(provider);
2621
+ return sessionStorage.getItem(storagePath$1(provider)) ?? generateOauthState(provider);
2621
2622
  }
2622
2623
  function generateOauthState(provider) {
2623
2624
  const state = `${provider}_${crypto.randomUUID()}`;
2624
- sessionStorage.setItem(storagePath(provider), state);
2625
+ sessionStorage.setItem(storagePath$1(provider), state);
2625
2626
  return state;
2626
2627
  }
2627
2628
 
@@ -2736,10 +2737,49 @@ const appleConfig = appleProvider.config;
2736
2737
  const appleAuthUrl = appleProvider.authUrl;
2737
2738
  const updateAppleAuthUrl = appleProvider.updateAuthUrl;
2738
2739
 
2740
+ //#endregion
2741
+ //#region src/auth/pkce.ts
2742
+ const storagePath = (provider) => PKCE_STORAGE_KEY_PREFIX + provider;
2743
+ const base64Url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2744
+ /**
2745
+ * Gets or generates the PKCE code verifier for a provider. It lives in session
2746
+ * storage so the callback page can send it with the authorization code.
2747
+ */
2748
+ function getPkceVerifier(provider) {
2749
+ const stored = sessionStorage.getItem(storagePath(provider));
2750
+ if (stored) return stored;
2751
+ const verifier = base64Url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
2752
+ sessionStorage.setItem(storagePath(provider), verifier);
2753
+ return verifier;
2754
+ }
2755
+ function clearPkceVerifier(provider) {
2756
+ sessionStorage.removeItem(storagePath(provider));
2757
+ }
2758
+ /** The S256 code challenge for a verifier. */
2759
+ async function pkceChallenge(verifier) {
2760
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
2761
+ return base64Url(new Uint8Array(digest));
2762
+ }
2763
+
2739
2764
  //#endregion
2740
2765
  //#region src/auth/providers/telegram.ts
2766
+ const TELEGRAM_AUTH_URL = "https://oauth.telegram.org/auth";
2741
2767
  const telegramConfig = shared("provider.telegram.config", () => atom(null));
2742
2768
  const telegramAuthUrl = shared("provider.telegram.authUrl", () => atom(null));
2769
+ const telegramRedirectUri = (config) => config.redirectUri ?? window.location.origin;
2770
+ const legacyAuthUrl = (config) => `${TELEGRAM_AUTH_URL}?bot_id=${encodeURIComponent(config.botId)}&origin=${encodeURIComponent(window.location.origin)}&return_to=${encodeURIComponent(window.location.origin + location.pathname)}`;
2771
+ const oidcAuthUrl = async (config) => {
2772
+ const params = new URLSearchParams({
2773
+ client_id: config.botId,
2774
+ redirect_uri: telegramRedirectUri(config),
2775
+ response_type: "code",
2776
+ scope: "openid profile",
2777
+ state: getOauthState("telegram"),
2778
+ code_challenge: await pkceChallenge(getPkceVerifier("telegram")),
2779
+ code_challenge_method: "S256"
2780
+ });
2781
+ return `${TELEGRAM_AUTH_URL}?${params.toString()}`;
2782
+ };
2743
2783
  /**
2744
2784
  * Updates the Telegram authentication URL using the configured settings
2745
2785
  */
@@ -2750,9 +2790,22 @@ function updateTelegramAuthUrl() {
2750
2790
  telegramAuthUrl.set(null);
2751
2791
  return;
2752
2792
  }
2753
- const url = `https://oauth.telegram.org/auth?bot_id=${encodeURIComponent(config.botId)}&origin=${encodeURIComponent(window.location.origin)}&return_to=${encodeURIComponent(window.location.origin + location.pathname)}`;
2754
- telegramAuthUrl.set(url);
2793
+ if (!config.oidc) {
2794
+ telegramAuthUrl.set(legacyAuthUrl(config));
2795
+ return;
2796
+ }
2797
+ telegramAuthUrl.set(null);
2798
+ oidcAuthUrl(config).then((url) => telegramAuthUrl.set(url));
2755
2799
  }
2800
+ /** The PKCE verifier and redirect URI the backend needs to redeem a Telegram authorization code. */
2801
+ const telegramCodeExchange = () => {
2802
+ const config = telegramConfig.get();
2803
+ if (!config) return void 0;
2804
+ return {
2805
+ codeVerifier: getPkceVerifier("telegram"),
2806
+ redirectUri: telegramRedirectUri(config)
2807
+ };
2808
+ };
2756
2809
 
2757
2810
  //#endregion
2758
2811
  //#region src/auth/url.ts
@@ -2783,10 +2836,10 @@ const OAUTH_PROVIDERS$1 = [
2783
2836
  */
2784
2837
  function detectProvider() {
2785
2838
  if (urlParams.has("tgWebAppData")) return "tgWebAppData";
2786
- if (urlParams.has("tgAuthResult")) return "tgAuthResult";
2787
- const id_token = urlParams.get("id_token");
2788
2839
  const state = urlParams.get("state");
2789
- if (id_token && state) {
2840
+ if (urlParams.has("code") && state && validateOAuthState("telegram", state)) return "telegram";
2841
+ if (urlParams.has("tgAuthResult")) return "tgAuthResult";
2842
+ if (urlParams.get("id_token") && state) {
2790
2843
  const providerFromState = extractProviderFromState(state);
2791
2844
  return OAUTH_PROVIDERS$1.find((p) => providerFromState === p && validateOAuthState(p, state));
2792
2845
  }
@@ -2798,6 +2851,7 @@ function detectProvider() {
2798
2851
  */
2799
2852
  function getProviderToken(provider) {
2800
2853
  if (provider === "tgWebAppData" || provider === "tgAuthResult") return urlParams.get(provider) || void 0;
2854
+ if (provider === "telegram") return urlParams.get("code") || void 0;
2801
2855
  if (provider === "google" || provider === "apple" || provider === "microsoft") return urlParams.get("id_token") || void 0;
2802
2856
  }
2803
2857
 
@@ -2833,7 +2887,8 @@ const hasProviderResponse = () => CREDENTIAL_PARAMS.some((key) => urlParams.has(
2833
2887
  const AUTH_URL_UPDATERS = {
2834
2888
  ["google"]: updateGoogleAuthUrl,
2835
2889
  ["apple"]: updateAppleAuthUrl,
2836
- ["microsoft"]: updateMicrosoftAuthUrl
2890
+ ["microsoft"]: updateMicrosoftAuthUrl,
2891
+ ["telegram"]: updateTelegramAuthUrl
2837
2892
  };
2838
2893
  /** Returns undefined when the half holds no credential, meaning it is the app's own and must not be touched. */
2839
2894
  const stripped = (query) => {
@@ -2869,6 +2924,7 @@ const completeOAuthCallback = () => {
2869
2924
  const provider = detectProvider();
2870
2925
  if (!provider) return;
2871
2926
  clearOauthState(provider);
2927
+ clearPkceVerifier(provider);
2872
2928
  AUTH_URL_UPDATERS[provider]?.();
2873
2929
  window.history.replaceState(window.history.state, "", withoutOAuthResponse(window.location.href));
2874
2930
  };
@@ -2981,30 +3037,31 @@ const logProviderExchangeFailure = (provider, credential, error) => {
2981
3037
  };
2982
3038
 
2983
3039
  //#endregion
2984
- //#region src/auth/social/socialState.ts
2985
- const socialStoragePath = (provider) => SOCIAL_CONNECT_KEY_PREFIX + provider;
2986
- /**
2987
- * Sets a flag indicating that a social provider connection is being attempted
2988
- * @param provider The provider identifier
2989
- */
2990
- function setSocialConnectAttempt(provider) {
2991
- sessionStorage.setItem(socialStoragePath(provider), "true");
2992
- }
2993
- /**
2994
- * Checks if there's a pending social provider connection attempt
2995
- * @param provider The provider identifier
2996
- * @returns True if there's a pending connection attempt, false otherwise
2997
- */
2998
- function hasSocialConnectAttempt(provider) {
2999
- return sessionStorage.getItem(socialStoragePath(provider)) === "true";
3000
- }
3040
+ //#region src/auth/api/refresh-tokens.ts
3001
3041
  /**
3002
- * Clears the social provider connection attempt flag
3003
- * @param provider The provider identifier
3042
+ * Exchange a provider credential for a Rixl session, or refresh an existing
3043
+ * Rixl session with a Bearer refresh token.
3044
+ *
3045
+ * Both go to `/auth/v1/token`, which needs no session: `token_type` is the
3046
+ * `AuthProvider` value and `refresh_token` carries the stored refresh token or
3047
+ * the provider's `id_token` / Telegram payload. `/auth/v1/providers/connect`
3048
+ * links a provider to an already signed-in user, so it cannot sign anyone in.
3004
3049
  */
3005
- function clearSocialConnectAttempt(provider) {
3006
- sessionStorage.removeItem(socialStoragePath(provider));
3007
- }
3050
+ const refreshTokens = async (provider, token, options) => {
3051
+ const body = {
3052
+ token_type: provider,
3053
+ refresh_token: token,
3054
+ country_code: options?.countryCode,
3055
+ origin: options?.origin,
3056
+ code_verifier: options?.codeVerifier,
3057
+ redirect_uri: options?.redirectUri
3058
+ };
3059
+ const { data } = await authV1TokenServiceRefreshToken({
3060
+ body,
3061
+ throwOnError: true
3062
+ });
3063
+ return data;
3064
+ };
3008
3065
 
3009
3066
  //#endregion
3010
3067
  //#region src/auth/initialization.ts
@@ -3035,254 +3092,6 @@ function createDeferred() {
3035
3092
  */
3036
3093
  const initDeferred = shared("initDeferred", createDeferred);
3037
3094
 
3038
- //#endregion
3039
- //#region src/auth/api/error-handlers.ts
3040
- /**
3041
- * Helper to create error functions - reduces bundle size by reusing error creation logic
3042
- */
3043
- const err = (message) => () => new Error(message);
3044
- /** Reusable error handlers for common cases - reduces repetitive error messages */
3045
- const commonErrors = {
3046
- unauthorized: err("User is not authorized"),
3047
- badRequest: err("Bad request"),
3048
- notFound: err("Not found"),
3049
- conflict: err("Resource already exists"),
3050
- forbidden: err("Forbidden"),
3051
- tooManyRequests: err("Too many requests")
3052
- };
3053
- /**
3054
- * Standard error handler for API errors with custom status code handling
3055
- */
3056
- const handleApiError = (error, statusHandlers) => {
3057
- if (error instanceof ApiError) {
3058
- const handler = statusHandlers[error.status];
3059
- if (handler) throw new ApiError(handler().message, error.status, error.endpoint, error.data);
3060
- throw error;
3061
- }
3062
- throw error;
3063
- };
3064
-
3065
- //#endregion
3066
- //#region src/auth/api/utils.ts
3067
- /**
3068
- * Generic helper for API calls to handle initialization and error handling
3069
- * @param fn The async function to execute
3070
- * @param errorMap A map of status codes to error functions
3071
- * @returns The result of the function execution
3072
- */
3073
- const apiCall = async (fn, errorMap = {}) => {
3074
- await initDeferred.promise;
3075
- try {
3076
- return await fn();
3077
- } catch (error) {
3078
- return handleApiError(error, errorMap);
3079
- }
3080
- };
3081
-
3082
- //#endregion
3083
- //#region src/auth/api/tokens.ts
3084
- function persistTokens(data) {
3085
- const tokens = data ?? {};
3086
- if (tokens.access_token && tokens.refresh_token && tokens.expires_in) {
3087
- setTokens(tokens.access_token, tokens.refresh_token, Number(tokens.expires_in));
3088
- return true;
3089
- }
3090
- return false;
3091
- }
3092
-
3093
- //#endregion
3094
- //#region src/auth/validation/base.ts
3095
- /**
3096
- * 🛡️ INPUT VALIDATION UTILITY
3097
- * Validates data before API calls and throws user-friendly errors
3098
- */
3099
- const validateInput = (schema, data) => {
3100
- try {
3101
- return v.parse(schema, data);
3102
- } catch (error) {
3103
- if (error instanceof v.ValiError) {
3104
- const firstError = error.issues[0];
3105
- throw new Error(firstError.message);
3106
- }
3107
- throw error;
3108
- }
3109
- };
3110
- const EmailSchema = v.pipe(v.string("Email must be text"), v.email("Please enter a valid email address"), v.minLength(1, "Email is required"));
3111
- const PasswordSchema = v.pipe(v.string("Password must be text"), v.minLength(8, "Password must be at least 8 characters long"), v.regex(/[A-Z]/, "Password must contain at least one uppercase letter"), v.regex(/[a-z]/, "Password must contain at least one lowercase letter"), v.regex(/[0-9]/, "Password must contain at least one number"));
3112
- /**
3113
- * Username validation schema
3114
- * - Must be 4-24 characters
3115
- * - Only letters, numbers, underscores, and periods
3116
- */
3117
- const UsernameSchema = v.pipe(v.string("Username must be text"), v.minLength(4, "Username must be 4-24 characters long"), v.maxLength(24, "Username must be 4-24 characters long"), v.regex(/^[a-z0-9_.]+$/, "Username can only contain lowercase letters, numbers, dots and underscores"));
3118
- /**
3119
- * Name validation schema
3120
- * - Must be 1-30 characters
3121
- */
3122
- const NameSchema = v.pipe(v.string("Name must be text"), v.minLength(1, "Name must be 1-30 characters long"), v.maxLength(30, "Name must be 1-30 characters long"));
3123
- /**
3124
- * Domain validation schema
3125
- * - Must be lowercase
3126
- * - Must contain at least one dot
3127
- * - Only alphanumeric characters, hyphens, and dots
3128
- * - Cannot start or end with hyphen in any part
3129
- */
3130
- const DomainSchema = v.pipe(v.string("Domain must be text"), v.trim(), v.toLowerCase(), v.minLength(1, "Domain is required"), v.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/, "Invalid domain format. Please enter a valid domain like company.com"));
3131
-
3132
- //#endregion
3133
- //#region src/auth/validation/auth.ts
3134
- const EmailAuthRequestSchema = v.object({
3135
- email: EmailSchema,
3136
- password: PasswordSchema
3137
- });
3138
- const RegisterRequestSchema = v.object({
3139
- email: EmailSchema,
3140
- password: PasswordSchema,
3141
- country_code: v.optional(v.string()),
3142
- subscribe_to_blog: v.optional(v.boolean())
3143
- });
3144
- const ResetPasswordRequestSchema = v.object({
3145
- token: v.string(),
3146
- new_password: PasswordSchema
3147
- });
3148
- const LoginOTPVerifyRequestSchema = v.object({
3149
- code: v.pipe(v.string("TOTP or backup code must be text"), v.minLength(1, "TOTP or backup code is required")),
3150
- session_id: v.pipe(v.string("Session ID must be text"), v.minLength(1, "Session ID is required"))
3151
- });
3152
- const ResendEmailRequestSchema = v.object({ email: EmailSchema });
3153
- const ChangeEmailRequestSchema = v.object({ new_email: EmailSchema });
3154
- const verifyEmailChangeRequestSchema = v.object({
3155
- code: v.pipe(v.string("OTP code must be text"), v.minLength(6, "OTP code must be at least 6 characters"), v.maxLength(8, "OTP code must be at most 8 characters"), v.regex(/^\d+$/, "OTP code must contain only numbers")),
3156
- new_email: v.optional(EmailSchema),
3157
- type: v.union([v.literal("verification"), v.literal("email_change")]),
3158
- verification_id: v.string("Verification ID is required")
3159
- });
3160
- /**
3161
- * Social provider schemas
3162
- */
3163
- const ConnectProviderSchema = v.object({
3164
- provider: v.pipe(v.string("Provider must be text"), v.minLength(1, "Provider is required")),
3165
- token: v.pipe(v.string("Token must be text"), v.minLength(1, "Token is required"))
3166
- });
3167
- /**
3168
- * OTP verification schema (for user OTP operations)
3169
- */
3170
- const VerifyOTPCodeSchema = v.object({ code: v.pipe(v.string("OTP code must be text"), v.minLength(6, "OTP code must be at least 6 characters"), v.regex(/^\d+$/, "OTP code must contain only numbers")) });
3171
-
3172
- //#endregion
3173
- //#region src/auth/social/socialConnections.ts
3174
- const PROVIDER_TO_PROTO = {
3175
- google: "EXTERNAL_ACCOUNT_PROVIDER_GOOGLE",
3176
- apple: "EXTERNAL_ACCOUNT_PROVIDER_APPLE",
3177
- microsoft: "EXTERNAL_ACCOUNT_PROVIDER_MICROSOFT",
3178
- facebook: "EXTERNAL_ACCOUNT_PROVIDER_FACEBOOK",
3179
- telegram: "EXTERNAL_ACCOUNT_PROVIDER_TELEGRAM"
3180
- };
3181
- const PROVIDER_FROM_PROTO = {
3182
- EXTERNAL_ACCOUNT_PROVIDER_GOOGLE: "google",
3183
- EXTERNAL_ACCOUNT_PROVIDER_APPLE: "apple",
3184
- EXTERNAL_ACCOUNT_PROVIDER_MICROSOFT: "microsoft",
3185
- EXTERNAL_ACCOUNT_PROVIDER_FACEBOOK: "facebook",
3186
- EXTERNAL_ACCOUNT_PROVIDER_TELEGRAM: "telegram"
3187
- };
3188
- const normalizeProviderType = (provider) => provider === "tgAuthResult" || provider === "tgWebAppData" ? "telegram" : provider;
3189
- const toProtoProvider = (provider) => {
3190
- const proto = PROVIDER_TO_PROTO[provider];
3191
- if (!proto) throw new Error(`Unknown provider: ${provider}`);
3192
- return proto;
3193
- };
3194
- const listSocials = async () => {
3195
- return apiCall(async () => {
3196
- const { data } = await authV1ProvidersServiceListProviders({ throwOnError: true });
3197
- return (data.providers ?? []).flatMap((p) => {
3198
- const provider = p.provider ? PROVIDER_FROM_PROTO[p.provider] : void 0;
3199
- if (!provider) return [];
3200
- return [{
3201
- provider,
3202
- username: p.username,
3203
- first_name: p.first_name,
3204
- last_name: p.last_name,
3205
- email_address: p.email_address,
3206
- image_url: p.image_url
3207
- }];
3208
- });
3209
- }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to list providers!") });
3210
- };
3211
- const connectSocialInternal = async (provider, token) => {
3212
- return apiCall(async () => {
3213
- const requestBody = validateInput(ConnectProviderSchema, {
3214
- provider,
3215
- token
3216
- });
3217
- const sessionToken = await getToken();
3218
- const headers = sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {};
3219
- const { data } = await authV1ProvidersServiceConnectProvider({
3220
- body: {
3221
- provider: toProtoProvider(normalizeProviderType(requestBody.provider)),
3222
- token: requestBody.token
3223
- },
3224
- headers,
3225
- throwOnError: true
3226
- });
3227
- persistTokens(data);
3228
- }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to connect provider!") });
3229
- };
3230
- const disconnectSocial = async (provider) => {
3231
- return apiCall(async () => {
3232
- await authV1ProvidersServiceDisconnectProvider({
3233
- path: { provider: toProtoProvider(provider) },
3234
- throwOnError: true
3235
- });
3236
- }, {
3237
- [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to disconnect provider!"),
3238
- [HTTP_STATUS.NOT_FOUND]: () => /* @__PURE__ */ new Error("Provider not found!"),
3239
- [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Cannot disconnect the last social provider!")
3240
- });
3241
- };
3242
- const connectSocial = (provider) => {
3243
- setSocialConnectAttempt(provider);
3244
- login(provider);
3245
- };
3246
-
3247
- //#endregion
3248
- //#region src/auth/api/refresh-tokens.ts
3249
- /**
3250
- * Exchange a provider credential for a Rixl session, or refresh an existing
3251
- * Rixl session with a Bearer refresh token.
3252
- *
3253
- * - For `AuthProvider.BEARER`, the call hits `/auth/v1/token` with the stored
3254
- * Rixl refresh token.
3255
- * - For OAuth/Telegram providers, the call hits `/auth/v1/providers/connect`
3256
- * with the provider's `id_token` or Telegram payload. The wire `provider`
3257
- * value is the `auth.v1.ExternalAccountProvider` enum (e.g.
3258
- * `EXTERNAL_ACCOUNT_PROVIDER_GOOGLE`), not the short SDK name.
3259
- */
3260
- const refreshTokens = async (provider, token, options) => {
3261
- if (provider === "Bearer") {
3262
- const { data } = await authV1TokenServiceRefreshToken({
3263
- body: {
3264
- token_type: "Bearer",
3265
- refresh_token: token,
3266
- country_code: options?.countryCode,
3267
- origin: options?.origin
3268
- },
3269
- throwOnError: true
3270
- });
3271
- return data;
3272
- }
3273
- const requestProvider = normalizeProviderType(provider);
3274
- const { data } = await authV1ProvidersServiceConnectProvider({
3275
- body: {
3276
- provider: toProtoProvider(requestProvider),
3277
- token,
3278
- country_code: options?.countryCode,
3279
- origin: options?.origin
3280
- },
3281
- throwOnError: true
3282
- });
3283
- return data;
3284
- };
3285
-
3286
3095
  //#endregion
3287
3096
  //#region src/auth/cookie/util.ts
3288
3097
  const splitOnFirstEquals = (pair) => {
@@ -3797,6 +3606,245 @@ function configureSdkClient() {
3797
3606
  });
3798
3607
  }
3799
3608
 
3609
+ //#endregion
3610
+ //#region src/auth/social/socialState.ts
3611
+ const socialStoragePath = (provider) => SOCIAL_CONNECT_KEY_PREFIX + provider;
3612
+ /**
3613
+ * Sets a flag indicating that a social provider connection is being attempted
3614
+ * @param provider The provider identifier
3615
+ */
3616
+ function setSocialConnectAttempt(provider) {
3617
+ sessionStorage.setItem(socialStoragePath(provider), "true");
3618
+ }
3619
+ /**
3620
+ * Checks if there's a pending social provider connection attempt
3621
+ * @param provider The provider identifier
3622
+ * @returns True if there's a pending connection attempt, false otherwise
3623
+ */
3624
+ function hasSocialConnectAttempt(provider) {
3625
+ return sessionStorage.getItem(socialStoragePath(provider)) === "true";
3626
+ }
3627
+ /**
3628
+ * Clears the social provider connection attempt flag
3629
+ * @param provider The provider identifier
3630
+ */
3631
+ function clearSocialConnectAttempt(provider) {
3632
+ sessionStorage.removeItem(socialStoragePath(provider));
3633
+ }
3634
+
3635
+ //#endregion
3636
+ //#region src/auth/api/error-handlers.ts
3637
+ /**
3638
+ * Helper to create error functions - reduces bundle size by reusing error creation logic
3639
+ */
3640
+ const err = (message) => () => new Error(message);
3641
+ /** Reusable error handlers for common cases - reduces repetitive error messages */
3642
+ const commonErrors = {
3643
+ unauthorized: err("User is not authorized"),
3644
+ badRequest: err("Bad request"),
3645
+ notFound: err("Not found"),
3646
+ conflict: err("Resource already exists"),
3647
+ forbidden: err("Forbidden"),
3648
+ tooManyRequests: err("Too many requests")
3649
+ };
3650
+ /**
3651
+ * Standard error handler for API errors with custom status code handling
3652
+ */
3653
+ const handleApiError = (error, statusHandlers) => {
3654
+ if (error instanceof ApiError) {
3655
+ const handler = statusHandlers[error.status];
3656
+ if (handler) throw new ApiError(handler().message, error.status, error.endpoint, error.data);
3657
+ throw error;
3658
+ }
3659
+ throw error;
3660
+ };
3661
+
3662
+ //#endregion
3663
+ //#region src/auth/api/utils.ts
3664
+ /**
3665
+ * Generic helper for API calls to handle initialization and error handling
3666
+ * @param fn The async function to execute
3667
+ * @param errorMap A map of status codes to error functions
3668
+ * @returns The result of the function execution
3669
+ */
3670
+ const apiCall = async (fn, errorMap = {}) => {
3671
+ await initDeferred.promise;
3672
+ try {
3673
+ return await fn();
3674
+ } catch (error) {
3675
+ return handleApiError(error, errorMap);
3676
+ }
3677
+ };
3678
+
3679
+ //#endregion
3680
+ //#region src/auth/api/tokens.ts
3681
+ function persistTokens(data) {
3682
+ const tokens = data ?? {};
3683
+ if (tokens.access_token && tokens.refresh_token && tokens.expires_in) {
3684
+ setTokens(tokens.access_token, tokens.refresh_token, Number(tokens.expires_in));
3685
+ return true;
3686
+ }
3687
+ return false;
3688
+ }
3689
+
3690
+ //#endregion
3691
+ //#region src/auth/validation/base.ts
3692
+ /**
3693
+ * 🛡️ INPUT VALIDATION UTILITY
3694
+ * Validates data before API calls and throws user-friendly errors
3695
+ */
3696
+ const validateInput = (schema, data) => {
3697
+ try {
3698
+ return v.parse(schema, data);
3699
+ } catch (error) {
3700
+ if (error instanceof v.ValiError) {
3701
+ const firstError = error.issues[0];
3702
+ throw new Error(firstError.message);
3703
+ }
3704
+ throw error;
3705
+ }
3706
+ };
3707
+ const EmailSchema = v.pipe(v.string("Email must be text"), v.email("Please enter a valid email address"), v.minLength(1, "Email is required"));
3708
+ const PasswordSchema = v.pipe(v.string("Password must be text"), v.minLength(8, "Password must be at least 8 characters long"), v.regex(/[A-Z]/, "Password must contain at least one uppercase letter"), v.regex(/[a-z]/, "Password must contain at least one lowercase letter"), v.regex(/[0-9]/, "Password must contain at least one number"));
3709
+ /**
3710
+ * Username validation schema
3711
+ * - Must be 4-24 characters
3712
+ * - Only letters, numbers, underscores, and periods
3713
+ */
3714
+ const UsernameSchema = v.pipe(v.string("Username must be text"), v.minLength(4, "Username must be 4-24 characters long"), v.maxLength(24, "Username must be 4-24 characters long"), v.regex(/^[a-z0-9_.]+$/, "Username can only contain lowercase letters, numbers, dots and underscores"));
3715
+ /**
3716
+ * Name validation schema
3717
+ * - Must be 1-30 characters
3718
+ */
3719
+ const NameSchema = v.pipe(v.string("Name must be text"), v.minLength(1, "Name must be 1-30 characters long"), v.maxLength(30, "Name must be 1-30 characters long"));
3720
+ /**
3721
+ * Domain validation schema
3722
+ * - Must be lowercase
3723
+ * - Must contain at least one dot
3724
+ * - Only alphanumeric characters, hyphens, and dots
3725
+ * - Cannot start or end with hyphen in any part
3726
+ */
3727
+ const DomainSchema = v.pipe(v.string("Domain must be text"), v.trim(), v.toLowerCase(), v.minLength(1, "Domain is required"), v.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/, "Invalid domain format. Please enter a valid domain like company.com"));
3728
+
3729
+ //#endregion
3730
+ //#region src/auth/validation/auth.ts
3731
+ const EmailAuthRequestSchema = v.object({
3732
+ email: EmailSchema,
3733
+ password: PasswordSchema
3734
+ });
3735
+ const RegisterRequestSchema = v.object({
3736
+ email: EmailSchema,
3737
+ password: PasswordSchema,
3738
+ country_code: v.optional(v.string()),
3739
+ subscribe_to_blog: v.optional(v.boolean())
3740
+ });
3741
+ const ResetPasswordRequestSchema = v.object({
3742
+ token: v.string(),
3743
+ new_password: PasswordSchema
3744
+ });
3745
+ const LoginOTPVerifyRequestSchema = v.object({
3746
+ code: v.pipe(v.string("TOTP or backup code must be text"), v.minLength(1, "TOTP or backup code is required")),
3747
+ session_id: v.pipe(v.string("Session ID must be text"), v.minLength(1, "Session ID is required"))
3748
+ });
3749
+ const ResendEmailRequestSchema = v.object({ email: EmailSchema });
3750
+ const ChangeEmailRequestSchema = v.object({ new_email: EmailSchema });
3751
+ const verifyEmailChangeRequestSchema = v.object({
3752
+ code: v.pipe(v.string("OTP code must be text"), v.minLength(6, "OTP code must be at least 6 characters"), v.maxLength(8, "OTP code must be at most 8 characters"), v.regex(/^\d+$/, "OTP code must contain only numbers")),
3753
+ new_email: v.optional(EmailSchema),
3754
+ type: v.union([v.literal("verification"), v.literal("email_change")]),
3755
+ verification_id: v.string("Verification ID is required")
3756
+ });
3757
+ /**
3758
+ * Social provider schemas
3759
+ */
3760
+ const ConnectProviderSchema = v.object({
3761
+ provider: v.pipe(v.string("Provider must be text"), v.minLength(1, "Provider is required")),
3762
+ token: v.pipe(v.string("Token must be text"), v.minLength(1, "Token is required"))
3763
+ });
3764
+ /**
3765
+ * OTP verification schema (for user OTP operations)
3766
+ */
3767
+ const VerifyOTPCodeSchema = v.object({ code: v.pipe(v.string("OTP code must be text"), v.minLength(6, "OTP code must be at least 6 characters"), v.regex(/^\d+$/, "OTP code must contain only numbers")) });
3768
+
3769
+ //#endregion
3770
+ //#region src/auth/social/socialConnections.ts
3771
+ const PROVIDER_TO_PROTO = {
3772
+ google: "EXTERNAL_ACCOUNT_PROVIDER_GOOGLE",
3773
+ apple: "EXTERNAL_ACCOUNT_PROVIDER_APPLE",
3774
+ microsoft: "EXTERNAL_ACCOUNT_PROVIDER_MICROSOFT",
3775
+ facebook: "EXTERNAL_ACCOUNT_PROVIDER_FACEBOOK",
3776
+ telegram: "EXTERNAL_ACCOUNT_PROVIDER_TELEGRAM"
3777
+ };
3778
+ const PROVIDER_FROM_PROTO = {
3779
+ EXTERNAL_ACCOUNT_PROVIDER_GOOGLE: "google",
3780
+ EXTERNAL_ACCOUNT_PROVIDER_APPLE: "apple",
3781
+ EXTERNAL_ACCOUNT_PROVIDER_MICROSOFT: "microsoft",
3782
+ EXTERNAL_ACCOUNT_PROVIDER_FACEBOOK: "facebook",
3783
+ EXTERNAL_ACCOUNT_PROVIDER_TELEGRAM: "telegram"
3784
+ };
3785
+ const normalizeProviderType = (provider) => provider === "tgAuthResult" || provider === "tgWebAppData" ? "telegram" : provider;
3786
+ const toProtoProvider = (provider) => {
3787
+ const proto = PROVIDER_TO_PROTO[provider];
3788
+ if (!proto) throw new Error(`Unknown provider: ${provider}`);
3789
+ return proto;
3790
+ };
3791
+ const listSocials = async () => {
3792
+ return apiCall(async () => {
3793
+ const { data } = await authV1ProvidersServiceListProviders({ throwOnError: true });
3794
+ return (data.providers ?? []).flatMap((p) => {
3795
+ const provider = p.provider ? PROVIDER_FROM_PROTO[p.provider] : void 0;
3796
+ if (!provider) return [];
3797
+ return [{
3798
+ provider,
3799
+ username: p.username,
3800
+ first_name: p.first_name,
3801
+ last_name: p.last_name,
3802
+ email_address: p.email_address,
3803
+ image_url: p.image_url
3804
+ }];
3805
+ });
3806
+ }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to list providers!") });
3807
+ };
3808
+ const connectSocialInternal = async (provider, token) => {
3809
+ return apiCall(async () => {
3810
+ const requestBody = validateInput(ConnectProviderSchema, {
3811
+ provider,
3812
+ token
3813
+ });
3814
+ const sessionToken = await getToken();
3815
+ const headers = sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {};
3816
+ const exchange = provider === "telegram" ? telegramCodeExchange() : void 0;
3817
+ const body = {
3818
+ provider: toProtoProvider(normalizeProviderType(requestBody.provider)),
3819
+ token: requestBody.token,
3820
+ code_verifier: exchange?.codeVerifier,
3821
+ redirect_uri: exchange?.redirectUri
3822
+ };
3823
+ const { data } = await authV1ProvidersServiceConnectProvider({
3824
+ body,
3825
+ headers,
3826
+ throwOnError: true
3827
+ });
3828
+ persistTokens(data);
3829
+ }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to connect provider!") });
3830
+ };
3831
+ const disconnectSocial = async (provider) => {
3832
+ return apiCall(async () => {
3833
+ await authV1ProvidersServiceDisconnectProvider({
3834
+ path: { provider: toProtoProvider(provider) },
3835
+ throwOnError: true
3836
+ });
3837
+ }, {
3838
+ [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to disconnect provider!"),
3839
+ [HTTP_STATUS.NOT_FOUND]: () => /* @__PURE__ */ new Error("Provider not found!"),
3840
+ [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Cannot disconnect the last social provider!")
3841
+ });
3842
+ };
3843
+ const connectSocial = (provider) => {
3844
+ setSocialConnectAttempt(provider);
3845
+ login(provider);
3846
+ };
3847
+
3800
3848
  //#endregion
3801
3849
  //#region src/auth/authConfig.ts
3802
3850
  const config = shared("authConfig", () => ({ loginRedirectUrl: void 0 }));
@@ -3887,7 +3935,8 @@ const handleInitPageApiError = (error) => {
3887
3935
  setAuthErrorAndClear("provider_exchange_failed", info.error || info.description, info.email);
3888
3936
  };
3889
3937
  const exchangeProviderToken = async (provider, token) => {
3890
- const result = await refreshTokens(provider, token);
3938
+ const options = provider === "telegram" ? telegramCodeExchange() : void 0;
3939
+ const result = await refreshTokens(provider, token, options);
3891
3940
  applyTokenExchangeResult(result);
3892
3941
  };
3893
3942
  const initPage = async () => {
@@ -3896,8 +3945,12 @@ const initPage = async () => {
3896
3945
  if (hasProviderResponse()) logUnusableProviderResponse();
3897
3946
  return;
3898
3947
  }
3948
+ if (provider === "telegram" && hasSocialConnectAttempt(provider)) return void 0;
3899
3949
  const token = getProviderToken(provider);
3900
3950
  if (!token) return void 0;
3951
+ await loginWithProviderToken(provider, token);
3952
+ };
3953
+ const loginWithProviderToken = async (provider, token) => {
3901
3954
  try {
3902
3955
  await exchangeProviderToken(provider, token);
3903
3956
  } catch (error) {
@@ -4619,8 +4672,10 @@ const initiateEmailChange = async (email) => {
4619
4672
  const addEmail = async (email) => {
4620
4673
  return apiCall(async () => {
4621
4674
  const validatedInput = validateInput(ResendEmailRequestSchema, { email });
4675
+ const limitedToken = limitedAccessToken.get();
4622
4676
  const { data } = await authV1EmailServiceAddEmail({
4623
4677
  body: validatedInput,
4678
+ ...limitedToken && { headers: { Authorization: `Bearer ${limitedToken}` } },
4624
4679
  throwOnError: true
4625
4680
  });
4626
4681
  if (data.verification_id) return {