@donotdev/oauth 0.0.7 → 0.0.8

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.
@@ -8,18 +8,24 @@ import type { OAuthAPI } from '@donotdev/core';
8
8
  * **Property-based access (clean and simple):**
9
9
  * ```typescript
10
10
  * // ✅ State from store (reactive, re-renders on change)
11
- * const loading = useOAuth('loading');
11
+ * const status = useOAuth('status');
12
12
  * const error = useOAuth('error');
13
13
  * const partners = useOAuth('partners');
14
+ * const connectedPartners = useOAuth('connectedPartners');
15
+ * const isAvailable = useOAuth('isAvailable');
14
16
  *
15
- * // ✅ Methods from store (stable, never re-renders)
17
+ * // ✅ Methods (stable, never re-renders)
16
18
  * const connect = useOAuth('connect');
17
19
  * await connect('github', 'api-access');
20
+ * const disconnect = useOAuth('disconnect');
21
+ * const handleCallback = useOAuth('handleCallback');
22
+ * const isConnected = useOAuth('isConnected');
23
+ * const getCredentials = useOAuth('getCredentials');
18
24
  * ```
19
25
  *
20
26
  * **How it works:**
21
- * - Store properties (loading, error, etc.) → Subscribe via Zustand (reactive)
22
- * - Methods (connect, disconnect, etc.) → Get from store (stable)
27
+ * - Store properties (status, error, partners, connectedPartners) → Subscribe via Zustand (reactive)
28
+ * - Methods (connect, disconnect, handleCallback, isConnected, getCredentials) → useCallback (stable)
23
29
  *
24
30
  * @template K - The property key from OAuthAPI
25
31
  * @param key - Property name to access
@@ -1 +1 @@
1
- {"version":3,"file":"useOAuth.d.ts","sourceRoot":"","sources":["../src/useOAuth.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,gBAAgB,CAAC;AAmBxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAyQtE"}
1
+ {"version":3,"file":"useOAuth.d.ts","sourceRoot":"","sources":["../src/useOAuth.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,gBAAgB,CAAC;AAmBxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAyQtE"}
package/dist/useOAuth.js CHANGED
@@ -1 +1 @@
1
- "use client";import{useCallback as S}from"react";import{DEGRADED_OAUTH_API as v,FRAMEWORK_FEATURES as F,getOAuthClientId as T,getOAuthPartnerConfig as R,getOAuthRedirectUri as b,handleError as C,isClient as h,redirectToExternalUrlWithErrorHandling as x,useFeatureConsent as L}from"@donotdev/core";import{useOAuthStore as a}from"./oauthStore";let A=!1,$=!1,y=!1;const O=new Set(["status","error","partners","connectedPartners"]);function H(t){const o=L(F.OAUTH),u=S(async(e,r="api-access")=>{if(!h()||!o)return;const i=a.getState();i.setLoading(!0),i.setError(null),i.setPartnerConnecting(e,!0);try{const n=T(e);if(!n)throw new Error(`${e.toUpperCase()}_CLIENT_ID not configured`);const s=b(e),{codeChallenge:g,codeVerifier:m}=await M(),E=`${e}_${p(32)}`,U=j(e,n,s,g,r,E);sessionStorage.setItem(`oauth_state_${e}`,E),sessionStorage.setItem(`oauth_purpose_${e}`,r),sessionStorage.setItem(`oauth_${e}_verifier`,m),await x(U,{},"Failed to redirect to OAuth provider");const P=a.getState();P.setLoading(!1),P.setPartnerConnecting(e,!1),A=!1}catch(n){const s=a.getState();s.setLoading(!1),s.setPartnerConnecting(e,!1);const g=n instanceof Error?n.message:"Failed to connect";A||(C(n,{userMessage:`Failed to connect to ${e}. Please try again.`,context:{partnerId:e},severity:"error"}),A=!0),s.setPartnerError(e,g)}},[o]),c=S(async e=>{if(!h()||!o)return;const r=a.getState();r.setLoading(!0),r.setError(null);try{r.setPartnerDisconnected(e),sessionStorage.removeItem(`oauth_${e}_verifier`),sessionStorage.removeItem(`oauth_${e}_credentials`),sessionStorage.removeItem(`oauth_state_${e}`),sessionStorage.removeItem(`oauth_purpose_${e}`),$=!1}catch(i){const n=a.getState(),s=i instanceof Error?i.message:"Failed to disconnect";$||(C(i,{userMessage:`Failed to disconnect from ${e}. Please try again.`,context:{partnerId:e},severity:"error"}),$=!0),n.setPartnerError(e,s)}finally{a.getState().setLoading(!1)}},[o]),l=S(async(e,r,i)=>{if(!h()||!o)return;const n=a.getState();n.setLoading(!0),n.setError(null);try{const s=sessionStorage.getItem(`oauth_state_${e}`);if(!s)throw new Error("OAuth state not found \u2014 possible CSRF attack");if(!s.startsWith(`${e}_`))throw new Error("OAuth state partnerId mismatch \u2014 possible CSRF attack");if(!i||i!==s)throw new Error("OAuth state mismatch \u2014 possible CSRF attack");sessionStorage.removeItem(`oauth_state_${e}`);const g=sessionStorage.getItem(`oauth_${e}_verifier`);if(!g)throw new Error("OAuth code verifier not found");const m=await D(e,r,g),E=sessionStorage.getItem(`oauth_purpose_${e}`)||"api-access";sessionStorage.removeItem(`oauth_purpose_${e}`),n.setPartnerConnected(e,m,E),sessionStorage.removeItem(`oauth_${e}_verifier`),y=!1}catch(s){const g=a.getState(),m=s instanceof Error?s.message:"Failed to handle callback";y||(C(s,{userMessage:`Failed to complete ${e} authentication. Please try again.`,context:{partnerId:e},severity:"error"}),y=!0),g.setPartnerError(e,m)}finally{a.getState().setLoading(!1)}},[o]),w=S(e=>!h()||!o?!1:a.getState().isConnected(e),[o]),f=S(e=>!h()||!o?null:a.getState().getCredentials(e),[o]),d={connect:u,disconnect:c,handleCallback:l,isConnected:w,getCredentials:f},_=a(e=>O.has(t)?t==="connectedPartners"?e.getConnectedPartners():e[t]:null);return!h()||!o?v[t]:t==="isAvailable"?!0:O.has(t)?_:t in d?d[t]:v[t]}async function M(){const t=p(128),u=new TextEncoder().encode(t),c=await crypto.subtle.digest("SHA-256",u);return{codeVerifier:t,codeChallenge:btoa(String.fromCharCode(...new Uint8Array(c))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}}function p(t){const o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",u=crypto.getRandomValues(new Uint8Array(t));return Array.from(u,c=>o[c%o.length]).join("")}function j(t,o,u,c,l,w){const f=R(t);if(!f)throw new Error(`OAuth partner ${t} not configured`);const d=f.endpoints.authUrl;if(!d)throw new Error(`OAuth partner ${t} has no auth URL configured`);const _=f.scopes[l]||f.scopes["api-access"]||[],e=Array.isArray(_)?_.join(","):"",r=new URLSearchParams({client_id:o,redirect_uri:u,response_type:"code",scope:e,state:w,code_challenge:c,code_challenge_method:"S256"});return`${d}?${r.toString()}`}async function D(t,o,u){const c=await fetch("/api/oauth/exchange",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({partnerId:t,code:o,codeVerifier:u})});if(!c.ok)throw new Error("Token exchange failed");const l=await c.json();if(typeof l!="object"||l===null||typeof l.accessToken!="string"||l.accessToken==="")throw new Error("Token exchange returned invalid credentials");return l}export{H as useOAuth};
1
+ "use client";import{useCallback as m}from"react";import{DEGRADED_OAUTH_API as I,FRAMEWORK_FEATURES as F,getOAuthClientId as T,getOAuthPartnerConfig as b,getOAuthRedirectUri as k,handleError as y,isClient as h,redirectToExternalUrlWithErrorHandling as R,useFeatureConsent as U}from"@donotdev/core";import{useOAuthStore as a}from"./oauthStore";let E=!1,A=!1,C=!1;const v=new Set(["status","error","partners","connectedPartners"]);function L(t){const r=U(F.OAUTH),c=m(async(e,s="api-access")=>{if(!h()||!r)return;const i=a.getState();i.setLoading(!0),i.setError(null),i.setPartnerConnecting(e,!0);try{const n=T(e);if(!n)throw new Error(`${e.toUpperCase()}_CLIENT_ID not configured`);const o=k(e),{codeChallenge:u,codeVerifier:f}=await x(),S=`${e}_${P(32)}`,O=j(e,n,o,u,s,S);sessionStorage.setItem(`oauth_state_${e}`,S),sessionStorage.setItem(`oauth_purpose_${e}`,s),sessionStorage.setItem(`oauth_${e}_verifier`,f),await R(O,{},"Failed to redirect to OAuth provider");const $=a.getState();$.setLoading(!1),$.setPartnerConnecting(e,!1),E=!1}catch(n){const o=a.getState();o.setLoading(!1),o.setPartnerConnecting(e,!1);const u=n instanceof Error?n.message:"Failed to connect";E||(y(n,{userMessage:`Failed to connect to ${e}. Please try again.`,context:{partnerId:e},severity:"error"}),E=!0),o.setPartnerError(e,u)}},[r]),g=m(async e=>{if(!h()||!r)return;const s=a.getState();s.setLoading(!0),s.setError(null);try{s.setPartnerDisconnected(e),sessionStorage.removeItem(`oauth_${e}_verifier`),sessionStorage.removeItem(`oauth_${e}_credentials`),sessionStorage.removeItem(`oauth_state_${e}`),sessionStorage.removeItem(`oauth_purpose_${e}`),A=!1}catch(i){const n=a.getState(),o=i instanceof Error?i.message:"Failed to disconnect";A||(y(i,{userMessage:`Failed to disconnect from ${e}. Please try again.`,context:{partnerId:e},severity:"error"}),A=!0),n.setPartnerError(e,o)}finally{a.getState().setLoading(!1)}},[r]),d=m(async(e,s,i)=>{if(!h()||!r)return;const n=a.getState();n.setLoading(!0),n.setError(null);try{const o=sessionStorage.getItem(`oauth_state_${e}`);if(!o)throw new Error("OAuth state not found \u2014 possible CSRF attack");if(!o.startsWith(`${e}_`))throw new Error("OAuth state partnerId mismatch \u2014 possible CSRF attack");if(!i||i!==o)throw new Error("OAuth state mismatch \u2014 possible CSRF attack");sessionStorage.removeItem(`oauth_state_${e}`);const u=sessionStorage.getItem(`oauth_${e}_verifier`);if(!u)throw new Error("OAuth code verifier not found");const f=await D(e,s,u),S=sessionStorage.getItem(`oauth_purpose_${e}`)||"api-access";sessionStorage.removeItem(`oauth_purpose_${e}`),n.setPartnerConnected(e,f,S),sessionStorage.removeItem(`oauth_${e}_verifier`),C=!1}catch(o){const u=a.getState(),f=o instanceof Error?o.message:"Failed to handle callback";C||(y(o,{userMessage:`Failed to complete ${e} authentication. Please try again.`,context:{partnerId:e},severity:"error"}),C=!0),u.setPartnerError(e,f)}finally{a.getState().setLoading(!1)}},[r]),w=m(e=>!h()||!r?!1:a.getState().isConnected(e),[r]),l=m(e=>!h()||!r?null:a.getState().getCredentials(e),[r]),p={connect:c,disconnect:g,handleCallback:d,isConnected:w,getCredentials:l},_=a(e=>v.has(t)?t==="connectedPartners"?e.getConnectedPartners():e[t]:null);return!h()||!r?I[t]:t==="isAvailable"?!0:v.has(t)?_:t in p?p[t]:I[t]}async function x(){const t=P(128),r=new TextEncoder().encode(t),c=await crypto.subtle.digest("SHA-256",r);return{codeVerifier:t,codeChallenge:btoa(String.fromCharCode(...new Uint8Array(c))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}}function P(t){const r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",c=crypto.getRandomValues(new Uint8Array(t));return Array.from(c,g=>r[g%r.length]).join("")}function j(t,r,c,g,d,w){const l=b(t);if(!l)throw new Error(`OAuth partner ${t} not configured`);const p=l.endpoints.authUrl;if(!p)throw new Error(`OAuth partner ${t} has no auth URL configured`);const _=l.scopes[d]||l.scopes["api-access"]||[],e=Array.isArray(_)?_.join(" "):"",s=new URLSearchParams({client_id:r,redirect_uri:c,response_type:"code",scope:e,state:w,code_challenge:g,code_challenge_method:"S256"});return`${p}?${s.toString()}`}async function D(t,r,c){const g=await fetch("/api/oauth/exchange",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({partnerId:t,code:r,codeVerifier:c})});if(!g.ok)throw new Error("Token exchange failed");const d=await g.json();if(typeof d!="object"||d===null||typeof d.accessToken!="string"||d.accessToken==="")throw new Error("Token exchange returned invalid credentials");return d}export{L as useOAuth};
@@ -1 +1 @@
1
- import{OAUTH_PARTNERS as o,getEnabledOAuthPartners as i}from"@donotdev/core";function s(){return Object.keys(o)}function a(){return i()}function u(){const n=a();return s().filter(t=>!n.includes(t))}function l(n){return o[n]}function c(n){const t=o[n];return t?`${t.name}OAuthButton`:""}function h(){s().forEach(n=>{const t=c(n),e=a().includes(n)})}function P(){const n=[],t=s();return t.forEach(e=>{const r=l(e);if(!r){n.push(`Partner '${e}' has no configuration`);return}r.name||n.push(`Partner '${e}' missing name`),r.endpoints?.authUrl||n.push(`Partner '${e}' missing auth URL`),r.endpoints?.tokenUrl||n.push(`Partner '${e}' missing token URL`),r.icon||n.push(`Partner '${e}' missing icon`)}),{isValid:n.length===0,issues:n,totalPartners:t.length,enabledPartners:a().length,disabledPartners:u().length}}export{s as getAllOAuthPartners,u as getDisabledOAuthPartners,a as getEnabledOAuthPartners,c as getOAuthPartnerButtonName,l as getOAuthPartnerConfig,h as logOAuthPartnerDiscovery,P as validateOAuthPartnerIntegrity};
1
+ import{OAUTH_PARTNERS as u,getEnabledOAuthPartners as c}from"@donotdev/core";function s(){return Object.keys(u)}function a(){return c()}function i(){const n=a();return s().filter(t=>!n.includes(t))}function o(n){return u[n]}function h(n){const t=u[n];return t?`${t.name}OAuthButton`:""}function l(){s().forEach(n=>{const t=h(n),e=a().includes(n)})}function g(){const n=[],t=s();return t.forEach(e=>{const r=o(e);if(!r){n.push(`Partner '${e}' has no configuration`);return}r.name||n.push(`Partner '${e}' missing name`),r.endpoints?.authUrl||n.push(`Partner '${e}' missing auth URL`),r.endpoints?.tokenUrl||n.push(`Partner '${e}' missing token URL`),r.icon||n.push(`Partner '${e}' missing icon`)}),{isValid:n.length===0,issues:n,totalPartners:t.length,enabledPartners:a().length,disabledPartners:i().length}}export{s as getAllOAuthPartners,i as getDisabledOAuthPartners,a as getEnabledOAuthPartners,h as getOAuthPartnerButtonName,o as getOAuthPartnerConfig,l as logOAuthPartnerDiscovery,g as validateOAuthPartnerIntegrity};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/oauth",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -24,14 +24,14 @@
24
24
  "scripts": {
25
25
  "dev": "tsc --noEmit --watch --listFiles false --listEmittedFiles false",
26
26
  "clean": "rimraf dist tsconfig.tsbuildinfo",
27
- "type-check": "tsc --noEmit"
27
+ "type-check": "bunx tsc --noEmit"
28
28
  },
29
29
  "dependencies": {
30
30
  "valibot": "^1.2.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@donotdev/components": "^0.0.17",
34
- "@donotdev/core": "^0.0.24",
33
+ "@donotdev/components": "^0.0.19",
34
+ "@donotdev/core": "^0.0.25",
35
35
  "firebase": "^12.5.0",
36
36
  "react": "^19.2.4",
37
37
  "react-dom": "^19.2.4"