authifyer-sdk 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.
@@ -0,0 +1,96 @@
1
+ import React, { useState } from 'react';
2
+ import { useAuth } from '../hooks';
3
+ import { AuthBox } from './AuthBox';
4
+
5
+ export const SignIn: React.FC = () => {
6
+ const { client, config } = useAuth();
7
+ const [email, setEmail] = useState('');
8
+ const [password, setPassword] = useState('');
9
+ const [error, setError] = useState('');
10
+ const [isLoading, setIsLoading] = useState(false);
11
+
12
+ // Defaults if config is missing
13
+ const showEmailPass = config?.emailPasswordEnabled ?? true;
14
+ const providers = config?.oauthProviders ?? ['google', 'github'];
15
+
16
+ const handleEmailLogin = async (e: React.FormEvent) => {
17
+ e.preventDefault();
18
+ setIsLoading(true);
19
+ setError('');
20
+ try {
21
+ await client.signInWithEmail(email, password);
22
+ } catch (err) {
23
+ setError('Invalid credentials');
24
+ } finally {
25
+ setIsLoading(false);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <AuthBox title="Sign in" subtitle="to continue to Authifyer">
31
+ {error && <p className="authifyer-error">{error}</p>}
32
+
33
+ {(providers.includes('google') || providers.includes('github')) && (
34
+ <div className="authifyer-form">
35
+ {providers.includes('google') && (
36
+ <button
37
+ className="authifyer-provider-btn"
38
+ onClick={() => client.signInWithOAuth('google')}
39
+ >
40
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
41
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
42
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
43
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
44
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
45
+ </svg>
46
+ Continue with Google
47
+ </button>
48
+ )}
49
+ {providers.includes('github') && (
50
+ <button
51
+ className="authifyer-provider-btn"
52
+ onClick={() => client.signInWithOAuth('github')}
53
+ >
54
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
55
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
56
+ </svg>
57
+ Continue with GitHub
58
+ </button>
59
+ )}
60
+ </div>
61
+ )}
62
+
63
+ {(providers.includes('google') || providers.includes('github')) && showEmailPass && (
64
+ <div className="authifyer-divider">or</div>
65
+ )}
66
+
67
+ {showEmailPass && (
68
+ <form onSubmit={handleEmailLogin} className="authifyer-form">
69
+ <div className="authifyer-form-group">
70
+ <label className="authifyer-label">Email address</label>
71
+ <input
72
+ type="email"
73
+ className="authifyer-input"
74
+ value={email}
75
+ onChange={e => setEmail(e.target.value)}
76
+ required
77
+ />
78
+ </div>
79
+ <div className="authifyer-form-group">
80
+ <label className="authifyer-label">Password</label>
81
+ <input
82
+ type="password"
83
+ className="authifyer-input"
84
+ value={password}
85
+ onChange={e => setPassword(e.target.value)}
86
+ required
87
+ />
88
+ </div>
89
+ <button type="submit" className="authifyer-submit-btn" disabled={isLoading}>
90
+ {isLoading ? 'Signing In...' : 'Continue'}
91
+ </button>
92
+ </form>
93
+ )}
94
+ </AuthBox>
95
+ );
96
+ };
@@ -0,0 +1,114 @@
1
+ import React, { useState } from 'react';
2
+ import { useAuth } from '../hooks';
3
+ import { AuthBox } from './AuthBox';
4
+
5
+ export const SignUp: React.FC = () => {
6
+ const { client, config } = useAuth();
7
+ const [email, setEmail] = useState('');
8
+ const [password, setPassword] = useState('');
9
+ const [error, setError] = useState('');
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [success, setSuccess] = useState(false);
12
+
13
+ // Defaults if config is missing
14
+ const showEmailPass = config?.emailPasswordEnabled ?? true;
15
+ const providers = config?.oauthProviders ?? ['google', 'github'];
16
+
17
+ const handleEmailSignup = async (e: React.FormEvent) => {
18
+ e.preventDefault();
19
+ setIsLoading(true);
20
+ setError('');
21
+ try {
22
+ await client.signUpWithEmail(email, password);
23
+ setSuccess(true);
24
+ } catch (err) {
25
+ setError('Registration failed. User might already exist.');
26
+ } finally {
27
+ setIsLoading(false);
28
+ }
29
+ };
30
+
31
+ if (success) {
32
+ return (
33
+ <AuthBox title="Welcome!">
34
+ <div className="authifyer-success">
35
+ <svg className="authifyer-success-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
37
+ </svg>
38
+ <div>
39
+ <h3 className="authifyer-success-title">Success</h3>
40
+ <p className="authifyer-success-text">Registration completed. Please verify your email.</p>
41
+ </div>
42
+ </div>
43
+ </AuthBox>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <AuthBox title="Create your account" subtitle="Welcome to Authifyer">
49
+ {error && <p className="authifyer-error">{error}</p>}
50
+
51
+ {(providers.includes('google') || providers.includes('github')) && (
52
+ <div className="authifyer-form">
53
+ {providers.includes('google') && (
54
+ <button
55
+ className="authifyer-provider-btn"
56
+ onClick={() => client.signInWithOAuth('google')}
57
+ >
58
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
59
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
60
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
61
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
62
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
63
+ </svg>
64
+ Continue with Google
65
+ </button>
66
+ )}
67
+ {providers.includes('github') && (
68
+ <button
69
+ className="authifyer-provider-btn"
70
+ onClick={() => client.signInWithOAuth('github')}
71
+ >
72
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
73
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
74
+ </svg>
75
+ Continue with GitHub
76
+ </button>
77
+ )}
78
+ </div>
79
+ )}
80
+
81
+ {(providers.includes('google') || providers.includes('github')) && showEmailPass && (
82
+ <div className="authifyer-divider">or</div>
83
+ )}
84
+
85
+ {showEmailPass && (
86
+ <form onSubmit={handleEmailSignup} className="authifyer-form">
87
+ <div className="authifyer-form-group">
88
+ <label className="authifyer-label">Email address</label>
89
+ <input
90
+ type="email"
91
+ className="authifyer-input"
92
+ value={email}
93
+ onChange={e => setEmail(e.target.value)}
94
+ required
95
+ />
96
+ </div>
97
+ <div className="authifyer-form-group">
98
+ <label className="authifyer-label">Password</label>
99
+ <input
100
+ type="password"
101
+ className="authifyer-input"
102
+ value={password}
103
+ onChange={e => setPassword(e.target.value)}
104
+ required
105
+ />
106
+ </div>
107
+ <button type="submit" className="authifyer-submit-btn" disabled={isLoading}>
108
+ {isLoading ? 'Signing Up...' : 'Continue'}
109
+ </button>
110
+ </form>
111
+ )}
112
+ </AuthBox>
113
+ );
114
+ };
@@ -0,0 +1,223 @@
1
+ /* authifyerui.css */
2
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
3
+
4
+ .authifyer-root {
5
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
6
+ color: #111827;
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ .authifyer-root * {
11
+ box-sizing: inherit;
12
+ }
13
+
14
+ .authifyer-card {
15
+ background-color: #ffffff;
16
+ border-radius: 12px;
17
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(0, 0, 0, 0.05);
18
+ width: 100%;
19
+ max-width: 400px;
20
+ margin: 0 auto;
21
+ overflow: hidden;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+
26
+ .authifyer-card-header {
27
+ padding: 32px 32px 24px;
28
+ text-align: center;
29
+ }
30
+
31
+ .authifyer-card-title {
32
+ margin: 0;
33
+ font-size: 20px;
34
+ font-weight: 600;
35
+ color: #111827;
36
+ }
37
+
38
+ .authifyer-card-subtitle {
39
+ margin: 8px 0 0;
40
+ font-size: 14px;
41
+ color: #6b7280;
42
+ }
43
+
44
+ .authifyer-card-content {
45
+ padding: 0 32px 32px;
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 20px;
49
+ }
50
+
51
+ .authifyer-error {
52
+ background-color: #fee2e2;
53
+ color: #991b1b;
54
+ padding: 12px 16px;
55
+ border-radius: 6px;
56
+ font-size: 14px;
57
+ font-weight: 500;
58
+ margin: 0;
59
+ }
60
+
61
+ .authifyer-provider-btn {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ gap: 8px;
66
+ width: 100%;
67
+ padding: 10px 16px;
68
+ background-color: #ffffff;
69
+ border: 1px solid #d1d5db;
70
+ border-radius: 6px;
71
+ color: #374151;
72
+ font-size: 14px;
73
+ font-weight: 500;
74
+ cursor: pointer;
75
+ transition: background-color 0.15s ease, box-shadow 0.15s ease;
76
+ }
77
+
78
+ .authifyer-provider-btn:hover {
79
+ background-color: #f9fafb;
80
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
81
+ }
82
+
83
+ .authifyer-divider {
84
+ display: flex;
85
+ align-items: center;
86
+ color: #9ca3af;
87
+ font-size: 12px;
88
+ text-transform: uppercase;
89
+ }
90
+
91
+ .authifyer-divider::before,
92
+ .authifyer-divider::after {
93
+ content: '';
94
+ flex: 1;
95
+ border-bottom: 1px solid #e5e7eb;
96
+ }
97
+
98
+ .authifyer-divider::before {
99
+ margin-right: 12px;
100
+ }
101
+
102
+ .authifyer-divider::after {
103
+ margin-left: 12px;
104
+ }
105
+
106
+ .authifyer-form {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 16px;
110
+ }
111
+
112
+ .authifyer-form-group {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 6px;
116
+ }
117
+
118
+ .authifyer-label {
119
+ font-size: 13px;
120
+ font-weight: 500;
121
+ color: #374151;
122
+ }
123
+
124
+ .authifyer-input {
125
+ width: 100%;
126
+ padding: 10px 12px;
127
+ border: 1px solid #d1d5db;
128
+ border-radius: 6px;
129
+ font-size: 14px;
130
+ color: #111827;
131
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
132
+ outline: none;
133
+ }
134
+
135
+ .authifyer-input:focus {
136
+ border-color: #6366f1;
137
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
138
+ }
139
+
140
+ .authifyer-submit-btn {
141
+ width: 100%;
142
+ padding: 10px 16px;
143
+ background-color: #2563eb;
144
+ color: #ffffff;
145
+ border: none;
146
+ border-radius: 6px;
147
+ font-size: 14px;
148
+ font-weight: 500;
149
+ cursor: pointer;
150
+ transition: background-color 0.15s ease;
151
+ margin-top: 8px;
152
+ }
153
+
154
+ .authifyer-submit-btn:hover:not(:disabled) {
155
+ background-color: #1d4ed8;
156
+ }
157
+
158
+ .authifyer-submit-btn:disabled {
159
+ opacity: 0.7;
160
+ cursor: not-allowed;
161
+ }
162
+
163
+ .authifyer-footer {
164
+ background-color: #f9fafb;
165
+ padding: 16px;
166
+ text-align: center;
167
+ border-top: 1px solid #f3f4f6;
168
+ display: flex;
169
+ justify-content: center;
170
+ align-items: center;
171
+ gap: 6px;
172
+ }
173
+
174
+ .authifyer-secured-text {
175
+ font-size: 12px;
176
+ color: #6b7280;
177
+ font-weight: 500;
178
+ }
179
+
180
+ .authifyer-brand {
181
+ font-size: 12px;
182
+ font-weight: 600;
183
+ color: #111827;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 4px;
187
+ }
188
+
189
+ .authifyer-logo-icon {
190
+ width: 14px;
191
+ height: 14px;
192
+ fill: currentColor;
193
+ color: #2563eb;
194
+ }
195
+
196
+ /* Success Message */
197
+ .authifyer-success {
198
+ text-align: center;
199
+ padding: 40px 32px;
200
+ display: flex;
201
+ flex-direction: column;
202
+ align-items: center;
203
+ gap: 16px;
204
+ }
205
+
206
+ .authifyer-success-icon {
207
+ width: 48px;
208
+ height: 48px;
209
+ color: #10b981;
210
+ }
211
+
212
+ .authifyer-success-title {
213
+ font-size: 18px;
214
+ font-weight: 600;
215
+ color: #111827;
216
+ margin: 0;
217
+ }
218
+
219
+ .authifyer-success-text {
220
+ font-size: 14px;
221
+ color: #6b7280;
222
+ margin: 0;
223
+ }
@@ -0,0 +1,13 @@
1
+ import { createContext } from 'react';
2
+ import { AuthifyerClient } from '@/core/client';
3
+ import { User, Session, ProjectConfig } from '@/core/types';
4
+
5
+ export interface AuthContextType {
6
+ client: AuthifyerClient;
7
+ user: User | null;
8
+ session: Session | null;
9
+ config: ProjectConfig | null;
10
+ isLoading: boolean;
11
+ }
12
+
13
+ export const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -0,0 +1,26 @@
1
+
2
+ import { useContext } from 'react';
3
+ import { AuthContext } from './context';
4
+
5
+ export const useAuth = () => {
6
+ const context = useContext(AuthContext);
7
+ if (context === undefined) {
8
+ throw new Error('useAuth must be used within an AuthifyerProvider');
9
+ }
10
+ return context;
11
+ };
12
+
13
+ export const useUser = () => {
14
+ const { user, isLoading } = useAuth();
15
+ return { user, isLoading, isSignedIn: !!user };
16
+ };
17
+
18
+ export const useSession = () => {
19
+ const { session, isLoading } = useAuth();
20
+ return { session, isLoading, isSignedIn: !!session };
21
+ };
22
+
23
+ export const useAuthifyer = () => {
24
+ const { client } = useAuth();
25
+ return client;
26
+ };
@@ -0,0 +1,42 @@
1
+ import { jwtDecode } from 'jwt-decode';
2
+ import { User, Session } from '@/core/types';
3
+
4
+ interface DecodedToken {
5
+ sub: string; // subjectId (authifyerId)
6
+ sid: string; // publicSessionId
7
+ pid?: string; // publicProjectId
8
+ scope: string; // 'global' or 'project'
9
+ email: string;
10
+ name: string;
11
+ exp: number;
12
+ }
13
+
14
+ export const parseToken = (token: string): Session | null => {
15
+ try {
16
+ const decoded = jwtDecode<DecodedToken>(token);
17
+ const now = Date.now() / 1000;
18
+
19
+ if (decoded.exp < now) {
20
+ return null;
21
+ }
22
+
23
+ const user: User = {
24
+ authifyerId: decoded.sub,
25
+ name: decoded.name,
26
+ email: decoded.email,
27
+ emailVerified: false, // JWT might not have this, need to fetch user details or infer
28
+ isActive: true,
29
+ provider: 'email', // Defaulting, might need to be in claims
30
+ };
31
+
32
+ return {
33
+ token,
34
+ sessionId: decoded.sid,
35
+ expireAt: decoded.exp,
36
+ user,
37
+ };
38
+ } catch (error) {
39
+ console.error('Invalid token', error);
40
+ return null;
41
+ }
42
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM",
8
+ "DOM.Iterable"
9
+ ],
10
+ "module": "ESNext",
11
+ "skipLibCheck": true,
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": false,
17
+ "declaration": true,
18
+ "emitDeclarationOnly": true,
19
+ "outDir": "dist",
20
+ "jsx": "react-jsx",
21
+ "strict": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "baseUrl": ".",
26
+ "paths": {
27
+ "@/*": [
28
+ "./src/*"
29
+ ]
30
+ }
31
+ },
32
+ "include": [
33
+ "src"
34
+ ]
35
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": [
10
+ "vite.config.ts"
11
+ ]
12
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src'),
10
+ },
11
+ },
12
+ build: {
13
+ lib: {
14
+ entry: path.resolve(__dirname, 'src/index.ts'),
15
+ name: 'AuthifyerSDK',
16
+ fileName: (format) => `authifyer-sdk.${format}.js`,
17
+ },
18
+ rollupOptions: {
19
+ external: ['react', 'react-dom'],
20
+ output: {
21
+ globals: {
22
+ react: 'React',
23
+ 'react-dom': 'ReactDOM',
24
+ },
25
+ },
26
+ },
27
+ },
28
+ });