flowlink-auth 2.7.3 → 2.7.5
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.css +694 -0
- package/dist/index.js +701 -1134
- package/package.json +4 -3
- package/src/ErrorBox.jsx +22 -0
- package/src/SignIn.jsx +212 -0
- package/src/SignUp.jsx +747 -0
- package/src/api.js +60 -0
- package/src/createAuthMiddleware.js +69 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +5 -0
- package/src/init.js +100 -0
- package/src/provider.js +261 -0
- package/src/securityUtils.js +151 -0
- package/src/useAuth.js +13 -0
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowlink-auth",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.5",
|
|
4
4
|
"description": "Custom auth library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"types": "
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"dist"
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "esbuild src/*.js src/*.jsx --outdir=dist --bundle --format=esm --loader:.js=jsx --loader:.jsx=jsx",
|
package/src/ErrorBox.jsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/ErrorBox.jsx
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export default function ErrorBox({ message }) {
|
|
5
|
+
const style = {
|
|
6
|
+
border: '1px solid #e53935',
|
|
7
|
+
background: '#fff6f6',
|
|
8
|
+
color: '#b71c1c',
|
|
9
|
+
padding: 12,
|
|
10
|
+
borderRadius: 8,
|
|
11
|
+
fontFamily: 'system-ui, Roboto, Arial',
|
|
12
|
+
maxWidth: 640,
|
|
13
|
+
margin: '8px auto'
|
|
14
|
+
}
|
|
15
|
+
return (
|
|
16
|
+
<div role="alert" style={style}>
|
|
17
|
+
<strong>flowlink Auth Error —</strong>
|
|
18
|
+
<div style={{ marginTop: 6 }}>{message}</div>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
package/src/SignIn.jsx
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/signin.jsx
|
|
2
|
+
'use client'
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
import { useAuth } from './provider.js'
|
|
5
|
+
|
|
6
|
+
export default function SignIn({ onSuccess } = {}) {
|
|
7
|
+
const {
|
|
8
|
+
publishableKey,
|
|
9
|
+
baseUrl,
|
|
10
|
+
redirect,
|
|
11
|
+
redirectTo,
|
|
12
|
+
user,
|
|
13
|
+
loadingUser,
|
|
14
|
+
completeLogin,
|
|
15
|
+
fetchMe,
|
|
16
|
+
setUser
|
|
17
|
+
} = useAuth()
|
|
18
|
+
|
|
19
|
+
const [email, setEmail] = useState('')
|
|
20
|
+
const [password, setPassword] = useState('')
|
|
21
|
+
const [loading, setLoading] = useState(false)
|
|
22
|
+
const [error, setError] = useState(null)
|
|
23
|
+
const [message, setMessage] = useState(null)
|
|
24
|
+
|
|
25
|
+
if (loadingUser) return null
|
|
26
|
+
|
|
27
|
+
if (user && redirect) {
|
|
28
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
29
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function submit(e) {
|
|
34
|
+
e.preventDefault()
|
|
35
|
+
setError(null)
|
|
36
|
+
setMessage(null)
|
|
37
|
+
|
|
38
|
+
if (!email || !password) {
|
|
39
|
+
setError('Email and password are required')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setLoading(true)
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const endpoint = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/login`
|
|
47
|
+
|
|
48
|
+
const res = await fetch(endpoint, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
credentials: 'include',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'x-publishable-key': publishableKey || ''
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({ email, password })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const ct = res.headers.get('content-type') || ''
|
|
59
|
+
let data = {}
|
|
60
|
+
if (ct.includes('application/json')) data = await res.json()
|
|
61
|
+
else {
|
|
62
|
+
const text = await res.text()
|
|
63
|
+
throw new Error(`Unexpected response (status ${res.status}): ${text.slice(0, 200)}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!res.ok) throw new Error(data.error || data.message || `Login failed (status ${res.status})`)
|
|
67
|
+
|
|
68
|
+
const serverUser = data.user ?? null
|
|
69
|
+
if (serverUser && typeof setUser === 'function') {
|
|
70
|
+
try { setUser(serverUser) } catch (_) {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof completeLogin === 'function') {
|
|
74
|
+
try {
|
|
75
|
+
await completeLogin()
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if (typeof fetchMe === 'function') await fetchMe()
|
|
78
|
+
}
|
|
79
|
+
} else if (typeof fetchMe === 'function') {
|
|
80
|
+
await fetchMe()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (onSuccess) {
|
|
84
|
+
try { onSuccess(data) } catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setMessage('Signed in. Redirecting...')
|
|
88
|
+
if (redirect) {
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
91
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
92
|
+
}, 250)
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
setError(err.message || 'Network error')
|
|
96
|
+
} finally {
|
|
97
|
+
setLoading(false)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- OAuth start flow (Google / GitHub) ---
|
|
102
|
+
async function startOAuthFlow(provider) {
|
|
103
|
+
setError(null)
|
|
104
|
+
setLoading(true)
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const rid =
|
|
108
|
+
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
109
|
+
? crypto.randomUUID()
|
|
110
|
+
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
111
|
+
|
|
112
|
+
// Use main app signin page as callback so user lands on /signin after flow
|
|
113
|
+
const callbackUrl = encodeURIComponent(`${window.location.origin}/signin`)
|
|
114
|
+
|
|
115
|
+
const sdkBase = (typeof process !== 'undefined' && process.env && process.env.NEXT_PUBLIC_FLOWLINK_BASE_URL)
|
|
116
|
+
|| baseUrl || 'http://localhost:3001'
|
|
117
|
+
const startUrl = `${sdkBase.replace(/\/+$/, '')}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
118
|
+
|
|
119
|
+
if (!publishableKey) {
|
|
120
|
+
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const res = await fetch(startUrl, {
|
|
124
|
+
method: 'GET',
|
|
125
|
+
headers: { 'x-publishable-key': publishableKey },
|
|
126
|
+
mode: 'cors'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const data = await res.json().catch(() => null)
|
|
130
|
+
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
131
|
+
if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
|
|
132
|
+
|
|
133
|
+
window.location.href = data.oauthUrl
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error('OAuth start error:', err)
|
|
136
|
+
setError(err?.message || 'OAuth start failed')
|
|
137
|
+
setLoading(false)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const handleGoogle = (e) => {
|
|
142
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
143
|
+
startOAuthFlow('google')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const handleGithub = (e) => {
|
|
147
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
148
|
+
startOAuthFlow('github')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div style={overlay}>
|
|
153
|
+
<div style={modal}>
|
|
154
|
+
<h2 style={title}>Sign in</h2>
|
|
155
|
+
<p style={subtitle}>Welcome back — enter your credentials.</p>
|
|
156
|
+
|
|
157
|
+
<form onSubmit={submit} style={{ width: '100%' }}>
|
|
158
|
+
<label style={label}>Email</label>
|
|
159
|
+
<input
|
|
160
|
+
style={input}
|
|
161
|
+
value={email}
|
|
162
|
+
onChange={e => setEmail(e.target.value)}
|
|
163
|
+
type="email"
|
|
164
|
+
required
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<label style={label}>Password</label>
|
|
168
|
+
<input
|
|
169
|
+
style={input}
|
|
170
|
+
type="password"
|
|
171
|
+
value={password}
|
|
172
|
+
onChange={e => setPassword(e.target.value)}
|
|
173
|
+
required
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
<div style={{ marginTop: 12 }}>
|
|
177
|
+
<button style={button} type="submit" disabled={loading}>
|
|
178
|
+
{loading ? 'Signing in…' : 'Sign in'}
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
|
|
183
|
+
<button type="button" onClick={handleGoogle} style={oauthButtonGoogle} disabled={loading}>
|
|
184
|
+
Continue with Google
|
|
185
|
+
</button>
|
|
186
|
+
|
|
187
|
+
<button type="button" onClick={handleGithub} style={oauthButtonGithub} disabled={loading}>
|
|
188
|
+
Continue with GitHub
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{error && <div style={errorBox}>{error}</div>}
|
|
193
|
+
{message && <div style={successBox}>{message}</div>}
|
|
194
|
+
</form>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* styles */
|
|
201
|
+
const overlay = { position: 'fixed', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.45)', zIndex: 9999, padding: 20 }
|
|
202
|
+
const modal = { width: '100%', maxWidth: 420, background: '#0f1724', color: '#fff', borderRadius: 12, padding: 22, boxShadow: '0 10px 30px rgba(2,6,23,0.6)', border: '1px solid rgba(255,255,255,0.04)' }
|
|
203
|
+
const title = { margin: 0, fontSize: 20, fontWeight: 600 }
|
|
204
|
+
const subtitle = { marginTop: 6, marginBottom: 14, color: '#cbd5e1', fontSize: 13 }
|
|
205
|
+
const label = { display: 'block', color: '#cbd5e1', fontSize: 13, marginTop: 8 }
|
|
206
|
+
const input = { width: '100%', padding: '10px 12px', marginTop: 6, borderRadius: 8, border: '1px solid rgba(255,255,255,0.06)', background: '#0b1220', color: '#fff', boxSizing: 'border-box' }
|
|
207
|
+
const button = { width: '100%', padding: '10px 12px', borderRadius: 8, background: 'linear-gradient(90deg,#06b6d4,#2563eb)', color: '#0b1220', border: 'none', fontWeight: 700, cursor: 'pointer' }
|
|
208
|
+
const oauthButtonGoogle = { flex: 1, padding: '10px 12px', borderRadius: 8, background: '#db4437', color: '#fff', border: 'none', cursor: 'pointer' }
|
|
209
|
+
const oauthButtonGithub = { flex: 1, padding: '10px 12px', borderRadius: 8, background: '#24292f', color: '#fff', border: 'none', cursor: 'pointer' }
|
|
210
|
+
const errorBox = { marginTop: 10, color: '#ffb4b4', fontSize: 13 }
|
|
211
|
+
const successBox = { marginTop: 10, color: '#bef264', fontSize: 13 }
|
|
212
|
+
|