deevoauth 1.4.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/README.md +222 -0
- package/app/api/internal/create-user/route.js +54 -0
- package/app/api/internal/developer/clients/route.js +122 -0
- package/app/api/internal/generate-code/route.js +41 -0
- package/app/api/oauth/token/route.js +115 -0
- package/app/api/oauth/userinfo/route.js +46 -0
- package/app/consent/page.jsx +202 -0
- package/app/dashboard/page.jsx +254 -0
- package/app/developers/page.jsx +287 -0
- package/app/globals.css +1041 -0
- package/app/layout.jsx +33 -0
- package/app/login/page.jsx +257 -0
- package/app/page.jsx +165 -0
- package/app/register/page.jsx +249 -0
- package/components/DeevoLogo.jsx +41 -0
- package/firebase.json +10 -0
- package/jsconfig.json +7 -0
- package/lib/auth-context.jsx +102 -0
- package/lib/firebase-admin.js +32 -0
- package/lib/firebase.js +18 -0
- package/next.config.mjs +9 -0
- package/package.json +20 -0
- package/public/deevo-logo.svg +3 -0
- package/sdk/README.md +216 -0
- package/sdk/build.js +30 -0
- package/sdk/deevo-oauth-1.4.5.tgz +0 -0
- package/sdk/dist/index.d.ts +69 -0
- package/sdk/dist/index.js +228 -0
- package/sdk/dist/index.mjs +222 -0
- package/sdk/package.json +39 -0
- package/sdk/src/index.d.ts +69 -0
- package/sdk/src/index.js +228 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { AuthProvider, useAuth } from '@/lib/auth-context';
|
|
7
|
+
|
|
8
|
+
function DeveloperContent() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const { user, loading } = useAuth();
|
|
11
|
+
const [clients, setClients] = useState([]);
|
|
12
|
+
const [loadingClients, setLoadingClients] = useState(true);
|
|
13
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
14
|
+
const [newApp, setNewApp] = useState({ name: '', redirectUri: '' });
|
|
15
|
+
const [creating, setCreating] = useState(false);
|
|
16
|
+
const [createdApp, setCreatedApp] = useState(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!loading && !user) {
|
|
20
|
+
router.push('/login');
|
|
21
|
+
}
|
|
22
|
+
}, [user, loading, router]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (user) {
|
|
26
|
+
fetchClients();
|
|
27
|
+
}
|
|
28
|
+
}, [user]);
|
|
29
|
+
|
|
30
|
+
const fetchClients = async () => {
|
|
31
|
+
try {
|
|
32
|
+
const idToken = await user.getIdToken();
|
|
33
|
+
const res = await fetch('/api/internal/developer/clients', {
|
|
34
|
+
headers: { Authorization: `Bearer ${idToken}` },
|
|
35
|
+
});
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
setClients(data.clients || []);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error('Failed to load clients:', err);
|
|
40
|
+
}
|
|
41
|
+
setLoadingClients(false);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleCreateApp = async (e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
if (!newApp.name || !newApp.redirectUri) return;
|
|
47
|
+
|
|
48
|
+
setCreating(true);
|
|
49
|
+
try {
|
|
50
|
+
const idToken = await user.getIdToken();
|
|
51
|
+
const res = await fetch('/api/internal/developer/clients', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
Authorization: `Bearer ${idToken}`,
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(newApp),
|
|
58
|
+
});
|
|
59
|
+
const data = await res.json();
|
|
60
|
+
if (data.error) {
|
|
61
|
+
alert('Server Error: ' + data.error);
|
|
62
|
+
} else {
|
|
63
|
+
setCreatedApp(data);
|
|
64
|
+
setNewApp({ name: '', redirectUri: '' });
|
|
65
|
+
setShowCreate(false);
|
|
66
|
+
fetchClients();
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Failed to create app:', err);
|
|
70
|
+
}
|
|
71
|
+
setCreating(false);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleDeleteClient = async (clientId) => {
|
|
75
|
+
if (!confirm('Are you sure? This will break any app using this client.')) return;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const idToken = await user.getIdToken();
|
|
79
|
+
await fetch(`/api/internal/developer/clients?clientId=${clientId}`, {
|
|
80
|
+
method: 'DELETE',
|
|
81
|
+
headers: { Authorization: `Bearer ${idToken}` },
|
|
82
|
+
});
|
|
83
|
+
fetchClients();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error('Failed to delete client:', err);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (loading || !user) {
|
|
90
|
+
return (
|
|
91
|
+
<div className="loading-overlay">
|
|
92
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 32, width: 'auto' }} />
|
|
93
|
+
<span className="spinner" style={{ width: 24, height: 24, color: 'var(--primary)', marginTop: 'var(--space-4)' }} />
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
<div className="bg-animated" />
|
|
101
|
+
|
|
102
|
+
{/* Navbar */}
|
|
103
|
+
<nav className="navbar">
|
|
104
|
+
<Link href="/" className="navbar-brand">
|
|
105
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 22, width: 'auto' }} />
|
|
106
|
+
</Link>
|
|
107
|
+
<div className="navbar-actions">
|
|
108
|
+
<Link href="/dashboard" className="btn btn-ghost" style={{ fontSize: 'var(--font-size-sm)' }}>
|
|
109
|
+
My Account
|
|
110
|
+
</Link>
|
|
111
|
+
</div>
|
|
112
|
+
</nav>
|
|
113
|
+
|
|
114
|
+
<div className="dashboard-grid" style={{ paddingTop: 'var(--space-8)', maxWidth: '900px' }}>
|
|
115
|
+
{/* Header */}
|
|
116
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
117
|
+
<div>
|
|
118
|
+
<h1 style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 700, letterSpacing: '-0.02em' }}>
|
|
119
|
+
Developer Console
|
|
120
|
+
</h1>
|
|
121
|
+
<p style={{ color: 'var(--on-surface-variant)', fontSize: 'var(--font-size-sm)', marginTop: 'var(--space-1)' }}>
|
|
122
|
+
Manage your OAuth applications
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
<button className="btn btn-primary" onClick={() => setShowCreate(true)} style={{ padding: '0.6rem 1.5rem' }}>
|
|
126
|
+
+ New App
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Created App Credentials (shown once after creation) */}
|
|
131
|
+
{createdApp && (
|
|
132
|
+
<div className="alert alert-success" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 'var(--space-4)' }}>
|
|
133
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
134
|
+
<strong>✓ App Created Successfully!</strong>
|
|
135
|
+
<button className="btn btn-ghost" onClick={() => setCreatedApp(null)} style={{ padding: '0.25rem 0.5rem', fontSize: 'var(--font-size-xs)' }}>
|
|
136
|
+
Dismiss
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
<p style={{ fontSize: 'var(--font-size-xs)', opacity: 0.8 }}>
|
|
140
|
+
Save these credentials now — the Client Secret will NOT be shown again.
|
|
141
|
+
</p>
|
|
142
|
+
<div style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-3)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', wordBreak: 'break-all' }}>
|
|
143
|
+
<div><span style={{ color: 'var(--on-surface-variant)' }}>Client ID:</span> {createdApp.clientId}</div>
|
|
144
|
+
<div style={{ marginTop: 'var(--space-2)' }}><span style={{ color: 'var(--on-surface-variant)' }}>Client Secret:</span> {createdApp.clientSecret}</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
{/* Create App Form */}
|
|
150
|
+
{showCreate && (
|
|
151
|
+
<div className="glass-card section-card">
|
|
152
|
+
<h2 className="section-title">Register New Application</h2>
|
|
153
|
+
<form onSubmit={handleCreateApp} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>
|
|
154
|
+
<div className="form-group">
|
|
155
|
+
<label className="form-label" htmlFor="app-name">Application Name</label>
|
|
156
|
+
<input
|
|
157
|
+
id="app-name"
|
|
158
|
+
className="form-input"
|
|
159
|
+
placeholder="My Deevo App"
|
|
160
|
+
value={newApp.name}
|
|
161
|
+
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
|
162
|
+
required
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="form-group">
|
|
166
|
+
<label className="form-label" htmlFor="app-redirect">Redirect URI</label>
|
|
167
|
+
<input
|
|
168
|
+
id="app-redirect"
|
|
169
|
+
className="form-input"
|
|
170
|
+
placeholder="https://myapp.com/auth/callback"
|
|
171
|
+
value={newApp.redirectUri}
|
|
172
|
+
onChange={(e) => setNewApp({ ...newApp, redirectUri: e.target.value })}
|
|
173
|
+
required
|
|
174
|
+
/>
|
|
175
|
+
<span className="form-error" style={{ color: 'var(--on-surface-variant)', fontSize: 'var(--font-size-xs)' }}>
|
|
176
|
+
The URL where users will be redirected after authentication
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div style={{ display: 'flex', gap: 'var(--space-3)' }}>
|
|
180
|
+
<button type="submit" className="btn btn-primary" disabled={creating}>
|
|
181
|
+
{creating ? 'Creating...' : 'Create Application'}
|
|
182
|
+
</button>
|
|
183
|
+
<button type="button" className="btn btn-ghost" onClick={() => setShowCreate(false)}>Cancel</button>
|
|
184
|
+
</div>
|
|
185
|
+
</form>
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
|
|
189
|
+
{/* Apps List */}
|
|
190
|
+
<div className="glass-card section-card">
|
|
191
|
+
<h2 className="section-title">Your Applications</h2>
|
|
192
|
+
{loadingClients ? (
|
|
193
|
+
<div style={{ textAlign: 'center', padding: 'var(--space-8)' }}>
|
|
194
|
+
<span className="spinner" style={{ width: 24, height: 24, color: 'var(--primary)', margin: '0 auto' }} />
|
|
195
|
+
</div>
|
|
196
|
+
) : clients.length === 0 ? (
|
|
197
|
+
<div style={{ textAlign: 'center', padding: 'var(--space-8) 0', color: 'var(--on-surface-variant)' }}>
|
|
198
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ margin: '0 auto var(--space-4)', opacity: 0.3 }}>
|
|
199
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z" />
|
|
200
|
+
<path d="M2 17l10 5 10-5" />
|
|
201
|
+
<path d="M2 12l10 5 10-5" />
|
|
202
|
+
</svg>
|
|
203
|
+
<p style={{ fontSize: 'var(--font-size-sm)' }}>No applications registered yet</p>
|
|
204
|
+
<p style={{ fontSize: 'var(--font-size-xs)', marginTop: 'var(--space-1)', opacity: 0.6 }}>
|
|
205
|
+
Create your first app to start integrating Deevo Auth
|
|
206
|
+
</p>
|
|
207
|
+
</div>
|
|
208
|
+
) : (
|
|
209
|
+
<div className="app-list">
|
|
210
|
+
{clients.map((client) => (
|
|
211
|
+
<div key={client.id} className="app-item" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 'var(--space-3)' }}>
|
|
212
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
213
|
+
<span className="app-item-name">{client.name}</span>
|
|
214
|
+
<button
|
|
215
|
+
className="btn btn-ghost"
|
|
216
|
+
style={{ fontSize: 'var(--font-size-xs)', color: 'var(--error)', padding: '0.25rem 0.5rem' }}
|
|
217
|
+
onClick={() => handleDeleteClient(client.id)}
|
|
218
|
+
>
|
|
219
|
+
Delete
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
<div style={{ fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface-variant)', wordBreak: 'break-all' }}>
|
|
223
|
+
<div>Client ID: <span style={{ color: 'var(--primary)' }}>{client.id}</span></div>
|
|
224
|
+
<div style={{ marginTop: '2px' }}>Redirect: <span style={{ color: 'var(--on-surface)' }}>{client.redirectUri}</span></div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* Integration Guide */}
|
|
233
|
+
<div className="glass-card section-card">
|
|
234
|
+
<h2 className="section-title">Quick Integration Guide</h2>
|
|
235
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)' }}>
|
|
236
|
+
<div>
|
|
237
|
+
<h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
|
|
238
|
+
1. Install the SDK
|
|
239
|
+
</h3>
|
|
240
|
+
<div style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-3)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)' }}>
|
|
241
|
+
npm install deevoauth
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div>
|
|
246
|
+
<h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
|
|
247
|
+
2. Configure in your app
|
|
248
|
+
</h3>
|
|
249
|
+
<pre style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-4)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)', overflow: 'auto', whiteSpace: 'pre-wrap' }}>
|
|
250
|
+
{`import { DeevoAuth } from 'deevoauth';
|
|
251
|
+
|
|
252
|
+
const deevo = new DeevoAuth({
|
|
253
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
254
|
+
clientSecret: 'YOUR_CLIENT_SECRET',
|
|
255
|
+
redirectUri: 'https://yourapp.com/auth/callback',
|
|
256
|
+
});`}
|
|
257
|
+
</pre>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div>
|
|
261
|
+
<h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
|
|
262
|
+
3. Redirect users to sign in
|
|
263
|
+
</h3>
|
|
264
|
+
<pre style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-4)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)', overflow: 'auto', whiteSpace: 'pre-wrap' }}>
|
|
265
|
+
{`// Redirect to Deevo login
|
|
266
|
+
const loginUrl = deevo.getAuthUrl();
|
|
267
|
+
window.location.href = loginUrl;
|
|
268
|
+
|
|
269
|
+
// In your callback route:
|
|
270
|
+
const { accessToken, user } = await deevo.handleCallback(code);
|
|
271
|
+
console.log(user.email, user.name);`}
|
|
272
|
+
</pre>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export default function DeveloperPage() {
|
|
282
|
+
return (
|
|
283
|
+
<AuthProvider>
|
|
284
|
+
<DeveloperContent />
|
|
285
|
+
</AuthProvider>
|
|
286
|
+
);
|
|
287
|
+
}
|