@vaiftech/auth 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 VAIF Technologies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # @vaiftech/auth
2
+
3
+ Comprehensive authentication SDK for VAIF with session management, OAuth integration, MFA support, and React hooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vaiftech/auth
9
+ # or
10
+ pnpm add @vaiftech/auth
11
+ # or
12
+ yarn add @vaiftech/auth
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { createAuthClient } from '@vaiftech/auth';
19
+
20
+ const auth = createAuthClient({
21
+ url: 'https://api.vaif.studio',
22
+ apiKey: 'your-project-key',
23
+ });
24
+
25
+ // Sign up
26
+ const { session, user } = await auth.signUp({
27
+ email: 'user@example.com',
28
+ password: 'secure-password',
29
+ name: 'John Doe',
30
+ });
31
+
32
+ // Sign in
33
+ const { session, user } = await auth.signInWithPassword({
34
+ email: 'user@example.com',
35
+ password: 'secure-password',
36
+ });
37
+
38
+ // Listen to auth changes
39
+ auth.onAuthStateChange((event) => {
40
+ console.log('Auth event:', event.event);
41
+ console.log('User:', event.session?.user);
42
+ });
43
+
44
+ // Sign out
45
+ await auth.signOut();
46
+ ```
47
+
48
+ ## Features
49
+
50
+ - **Multiple Auth Methods**: Email/password, OAuth, Magic Link, OTP, Anonymous
51
+ - **Session Management**: Automatic token refresh, session persistence
52
+ - **MFA Support**: TOTP, SMS, Email verification
53
+ - **OAuth Providers**: Google, GitHub, Microsoft, Apple, Discord, and more
54
+ - **React Integration**: Context provider and hooks
55
+ - **Storage Adapters**: localStorage, sessionStorage, cookies, or custom
56
+ - **SSR Support**: Works with Next.js, Remix, and other frameworks
57
+
58
+ ## React Usage
59
+
60
+ ```tsx
61
+ import { AuthProvider, useAuth } from '@vaiftech/auth/react';
62
+
63
+ function App() {
64
+ return (
65
+ <AuthProvider config={{ url: 'https://api.vaif.studio', apiKey: 'your-key' }}>
66
+ <YourApp />
67
+ </AuthProvider>
68
+ );
69
+ }
70
+
71
+ function LoginPage() {
72
+ const { user, signInWithPassword, signInWithOAuth, isLoading } = useAuth();
73
+
74
+ if (isLoading) return <Loading />;
75
+ if (user) return <Redirect to="/dashboard" />;
76
+
77
+ return (
78
+ <div>
79
+ <form onSubmit={async (e) => {
80
+ e.preventDefault();
81
+ await signInWithPassword({ email, password });
82
+ }}>
83
+ <input type="email" value={email} onChange={...} />
84
+ <input type="password" value={password} onChange={...} />
85
+ <button type="submit">Sign In</button>
86
+ </form>
87
+
88
+ <button onClick={() => signInWithOAuth({ provider: 'google' })}>
89
+ Sign in with Google
90
+ </button>
91
+ </div>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## OAuth
97
+
98
+ ```typescript
99
+ // Sign in with OAuth provider
100
+ await auth.signInWithOAuth({
101
+ provider: 'google',
102
+ redirectTo: 'https://myapp.com/auth/callback',
103
+ scopes: ['email', 'profile'],
104
+ });
105
+
106
+ // Handle OAuth callback (automatic if detectSessionInUrl is true)
107
+ // Or manually:
108
+ const session = await auth.handleOAuthCallback(code, state);
109
+
110
+ // Link additional provider
111
+ await auth.linkIdentity('github');
112
+
113
+ // Unlink provider
114
+ await auth.unlinkIdentity('github');
115
+ ```
116
+
117
+ ## MFA
118
+
119
+ ```typescript
120
+ // Enroll TOTP
121
+ const { qrCode, secret, backupCodes } = await auth.enrollMFA({ type: 'totp' });
122
+
123
+ // Verify and enable MFA
124
+ await auth.verifyMFA({ factorId, code: '123456' });
125
+
126
+ // During login with MFA
127
+ const result = await auth.signInWithPassword({ email, password });
128
+ if ('mfaRequired' in result) {
129
+ const session = await auth.verifyMFAChallenge(result.mfaToken, '123456');
130
+ }
131
+ ```
132
+
133
+ ## Session Management
134
+
135
+ ```typescript
136
+ // Get current session
137
+ const session = await auth.getSession();
138
+
139
+ // Get current user
140
+ const user = await auth.getUser();
141
+
142
+ // Refresh session manually
143
+ await auth.refreshSession();
144
+
145
+ // List all sessions
146
+ const sessions = await auth.listSessions();
147
+
148
+ // Revoke a session
149
+ await auth.revokeSession(sessionId);
150
+
151
+ // Revoke all other sessions
152
+ await auth.revokeOtherSessions();
153
+ ```
154
+
155
+ ## Configuration
156
+
157
+ ```typescript
158
+ const auth = createAuthClient({
159
+ // Required
160
+ url: 'https://api.vaif.studio',
161
+
162
+ // Optional
163
+ apiKey: 'your-project-key',
164
+ headers: { 'x-custom-header': 'value' },
165
+ storage: localStorage, // or sessionStorage, cookieStorage(), or custom
166
+ storageKey: 'vaif.auth.',
167
+ autoRefreshToken: true,
168
+ persistSession: true,
169
+ detectSessionInUrl: true,
170
+ flowType: 'implicit', // or 'pkce'
171
+ debug: false,
172
+ });
173
+ ```
174
+
175
+ ## License
176
+
177
+ MIT License - VAIF Technologies
@@ -0,0 +1 @@
1
+ function l(i){return "mfaRequired"in i&&i.mfaRequired===true}var a=class i extends Error{constructor(e,t,s,r){super(e),this.name="AuthError",this.code=t,this.status=s,this.details=r;}static isAuthError(e){return e instanceof i}},m=class extends a{constructor(e="Session has expired"){super(e,"session_expired",401),this.name="SessionExpiredError";}},S=class extends a{constructor(e="Invalid email or password"){super(e,"invalid_credentials",401),this.name="InvalidCredentialsError";}};function n(){return typeof window<"u"&&typeof window.document<"u"}function y(){if(!n())return false;try{let i="__vaif_test__";return window.localStorage.setItem(i,i),window.localStorage.removeItem(i),!0}catch{return false}}var u=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},c=class{getItem(e){if(!n())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.localStorage.removeItem(e);}catch{}}},p=class{getItem(e){if(!n())return null;try{return window.sessionStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.sessionStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.sessionStorage.removeItem(e);}catch{}}},g=class{constructor(e){this.options={secure:n()&&window.location.protocol==="https:",sameSite:"lax",path:"/",...e};}getItem(e){if(!n())return null;try{let t=document.cookie.split(";");for(let s of t){let[r,o]=s.trim().split("=");if(r===e)return decodeURIComponent(o)}return null}catch{return null}}setItem(e,t){if(n())try{let s=`${e}=${encodeURIComponent(t)}`;s+=`; path=${this.options.path}`,s+=`; samesite=${this.options.sameSite}`,this.options.secure&&(s+="; secure"),this.options.domain&&(s+=`; domain=${this.options.domain}`),this.options.maxAge&&(s+=`; max-age=${this.options.maxAge}`),document.cookie=s;}catch{}}removeItem(e){if(n())try{document.cookie=`${e}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;}catch{}}};function _(){return y()?new c:new u}var d=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},A=new u,P=y()?new c:A,T=n()?new p:A,I=i=>new g(i);var k={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},v=60*1e3,f=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.config={...k,...e,headers:e.headers||{},storage:e.storage||_()},this.storage=new d(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&n()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true;}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let s=await this._fetchUser(e),r={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:s};return this.currentSession=r,await this._persistSession(r),this._notifyListeners("SIGNED_IN",r),this._setupAutoRefresh(r),{session:r,user:s}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=s,await this._persistSession(s),this._notifyListeners("TOKEN_REFRESHED",s),this._setupAutoRefresh(s),s}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return n()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let s=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return n()&&(window.location.href=s.url),s}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let s=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=s.session,await this._persistSession(s.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",s.session),this._setupAutoRefresh(s.session),s}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let s=`${this.config.url}${e}`,r={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(r["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(r.Authorization=`Bearer ${this.currentSession.accessToken}`);let o=await fetch(s,{...t,headers:r});if(!o.ok){let h=await o.json().catch(()=>({}));throw new a(h.message||"Request failed",this._mapErrorCode(o.status,h.code),o.status,h)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new a("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new a("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!n())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),s=e.get("access_token")||t.get("access_token"),r=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!s)return null;try{let h=await this._fetchUser(s),w={accessToken:s,refreshToken:r||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:h};return window.history.replaceState(null,"",window.location.pathname),w}catch(h){return this._log("Failed to handle URL session:",h),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,s=Date.now(),r=Math.max(0,t-s-v);this._log(`Setting up auto refresh in ${Math.round(r/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},r);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let s={event:e,session:t};this.listeners.forEach(r=>{try{r(s);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let s={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(s[t])return s[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function C(i){return new f(i)}export{l as a,a as b,m as c,S as d,n as e,_ as f,d as g,A as h,P as i,T as j,I as k,f as l,C as m};