@powerhousedao/academy 5.1.0-staging.0 → 5.1.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/CHANGELOG.md +46 -1148
- package/blog/BeyondCommunication-ABlueprintForDevelopment.md +1 -2
- package/blog/TheChallengeOfChange.md +0 -1
- package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
- package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +27 -24
- package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +10 -155
- package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +35 -122
- package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +155 -178
- package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
- package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +22 -62
- package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
- package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +44 -75
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +22 -28
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +31 -28
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +206 -211
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +62 -176
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +0 -21
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +319 -309
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +0 -4
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +0 -4
- package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
- package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +35 -111
- package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +79 -195
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +241 -435
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +27 -388
- package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +7 -95
- package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +2 -6
- package/docs/academy/04-APIReferences/01-ReactHooks.md +501 -291
- package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +39 -7
- package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
- package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
- package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +24 -72
- package/docs/academy/08-Glossary.md +0 -7
- package/docusaurus.config.ts +3 -28
- package/package.json +1 -1
- package/sidebars.ts +13 -49
- package/src/css/custom.css +18 -26
- package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -425
- package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -557
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/Modules.png +0 -0
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/VetraStudioDrive.png +0 -0
- package/docs/academy/02-MasteryTrack/05-Launch/05-DockerDeployment.md +0 -384
- package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
- package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
- package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
- package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
- package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
- package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
- package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
- package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
- package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
- package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
- package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
- package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
- package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
- package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
- package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
- package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
- package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
- package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
- package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
- package/static/img/Vetra-logo-dark.svg +0 -11
- package/static/img/vetra-logo-light.svg +0 -11
- /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
- /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
# Authentication Guide
|
|
2
|
-
|
|
3
|
-
Comprehensive guide to implementing authentication with the Renown SDK.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Overview](#overview)
|
|
8
|
-
- [Authentication Flow](#authentication-flow)
|
|
9
|
-
- [Setup](#setup)
|
|
10
|
-
- [Implementation](#implementation)
|
|
11
|
-
- [Advanced Patterns](#advanced-patterns)
|
|
12
|
-
- [Security Considerations](#security-considerations)
|
|
13
|
-
|
|
14
|
-
## Overview
|
|
15
|
-
|
|
16
|
-
The Renown SDK provides a complete authentication system that:
|
|
17
|
-
|
|
18
|
-
1. **Auto-initializes** the Renown SDK and ConnectCrypto (zero configuration!)
|
|
19
|
-
2. **Manages** user sessions across page reloads
|
|
20
|
-
3. **Handles** authentication redirects from Renown portal
|
|
21
|
-
4. **Fetches** user profile data automatically
|
|
22
|
-
5. **Provides** React hooks, components, and context for easy integration
|
|
23
|
-
|
|
24
|
-
## Authentication Flow
|
|
25
|
-
|
|
26
|
-
### 1. Initial Load
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
User visits app
|
|
30
|
-
↓
|
|
31
|
-
RenownUserProvider initializes
|
|
32
|
-
↓
|
|
33
|
-
Check sessionStorage for existing session
|
|
34
|
-
↓
|
|
35
|
-
├─ Session found → Restore user → Authorized
|
|
36
|
-
└─ No session → Check URL params → Initialize SDK → Not Authorized
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. Login Flow
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
User clicks "Login"
|
|
43
|
-
↓
|
|
44
|
-
openRenown() called
|
|
45
|
-
↓
|
|
46
|
-
Redirect to Renown Portal
|
|
47
|
-
↓
|
|
48
|
-
User authenticates
|
|
49
|
-
↓
|
|
50
|
-
Redirect back to app with DID
|
|
51
|
-
↓
|
|
52
|
-
handleRenownReturn() processes
|
|
53
|
-
↓
|
|
54
|
-
login() called
|
|
55
|
-
↓
|
|
56
|
-
Fetch user profile
|
|
57
|
-
↓
|
|
58
|
-
Store in sessionStorage
|
|
59
|
-
↓
|
|
60
|
-
Update auth state → Authorized
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 3. Session Restoration
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
User refreshes page
|
|
67
|
-
↓
|
|
68
|
-
RenownUserProvider checks sessionStorage
|
|
69
|
-
↓
|
|
70
|
-
Valid session found
|
|
71
|
-
↓
|
|
72
|
-
Fetch latest profile data
|
|
73
|
-
↓
|
|
74
|
-
Restore auth state → Authorized
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Setup
|
|
78
|
-
|
|
79
|
-
### Step 1: Wrap Your App with RenownUserProvider
|
|
80
|
-
|
|
81
|
-
The RenownUserProvider automatically initializes the Renown SDK - no manual setup required!
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
// app/layout.tsx (Next.js App Router)
|
|
85
|
-
import { RenownUserProvider } from '@renown/sdk'
|
|
86
|
-
|
|
87
|
-
export default function RootLayout({
|
|
88
|
-
children,
|
|
89
|
-
}: {
|
|
90
|
-
children: React.ReactNode
|
|
91
|
-
}) {
|
|
92
|
-
return (
|
|
93
|
-
<html lang="en">
|
|
94
|
-
<body>
|
|
95
|
-
<RenownUserProvider>
|
|
96
|
-
{children}
|
|
97
|
-
</RenownUserProvider>
|
|
98
|
-
</body>
|
|
99
|
-
</html>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
That's it! The SDK is now initialized and ready to use.
|
|
105
|
-
|
|
106
|
-
### Optional: Customize Configuration
|
|
107
|
-
|
|
108
|
-
You can customize the Renown URL, network, and chain:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
<RenownUserProvider
|
|
112
|
-
renownUrl={process.env.NEXT_PUBLIC_RENOWN_URL || 'https://www.renown.id'}
|
|
113
|
-
networkId="eip155"
|
|
114
|
-
chainId="1"
|
|
115
|
-
>
|
|
116
|
-
{children}
|
|
117
|
-
</RenownUserProvider>
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Optional: Add Loading/Error Screens
|
|
121
|
-
|
|
122
|
-
Provide custom UI for initialization states:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
<RenownUserProvider
|
|
126
|
-
loadingComponent={
|
|
127
|
-
<div className="loading-screen">
|
|
128
|
-
<Spinner />
|
|
129
|
-
<p>Initializing authentication...</p>
|
|
130
|
-
</div>
|
|
131
|
-
}
|
|
132
|
-
errorComponent={(error, retry) => (
|
|
133
|
-
<div className="error-screen">
|
|
134
|
-
<h2>Authentication Failed</h2>
|
|
135
|
-
<p>{error.message}</p>
|
|
136
|
-
<button onClick={retry}>Try Again</button>
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
139
|
-
>
|
|
140
|
-
{children}
|
|
141
|
-
</RenownUserProvider>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Implementation
|
|
145
|
-
|
|
146
|
-
### Using the RenownAuthButton Component
|
|
147
|
-
|
|
148
|
-
The simplest way to add authentication is to use the built-in `RenownAuthButton` component:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
'use client'
|
|
152
|
-
|
|
153
|
-
import { RenownAuthButton } from '@renown/sdk'
|
|
154
|
-
|
|
155
|
-
export function Header() {
|
|
156
|
-
return (
|
|
157
|
-
<header>
|
|
158
|
-
<h1>My App</h1>
|
|
159
|
-
<RenownAuthButton showLogoutButton showUsername />
|
|
160
|
-
</header>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Custom Login Component with useUser Hook
|
|
166
|
-
|
|
167
|
-
For full control, build your own component using the `useUser` hook:
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
'use client'
|
|
171
|
-
|
|
172
|
-
import { useUser } from '@renown/sdk'
|
|
173
|
-
|
|
174
|
-
export function CustomRenownAuthButton() {
|
|
175
|
-
const { user, loginStatus, isLoading, openRenown, logout } = useUser()
|
|
176
|
-
|
|
177
|
-
if (isLoading) {
|
|
178
|
-
return (
|
|
179
|
-
<button disabled>
|
|
180
|
-
<Spinner />
|
|
181
|
-
Loading...
|
|
182
|
-
</button>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (loginStatus === 'authorized' && user) {
|
|
187
|
-
return (
|
|
188
|
-
<div className="flex items-center gap-2">
|
|
189
|
-
{user.avatar && (
|
|
190
|
-
<img
|
|
191
|
-
src={user.avatar}
|
|
192
|
-
alt={user.name || 'User'}
|
|
193
|
-
className="w-8 h-8 rounded-full"
|
|
194
|
-
/>
|
|
195
|
-
)}
|
|
196
|
-
<span>{user.name || user.did.slice(0, 15)}...</span>
|
|
197
|
-
<button onClick={logout}>Logout</button>
|
|
198
|
-
</div>
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return (
|
|
203
|
-
<button onClick={openRenown}>
|
|
204
|
-
Login with Renown
|
|
205
|
-
</button>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Protected Route Component
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
'use client'
|
|
214
|
-
|
|
215
|
-
import { useUser } from '@renown/sdk'
|
|
216
|
-
import { useRouter } from 'next/navigation'
|
|
217
|
-
import { useEffect } from 'react'
|
|
218
|
-
|
|
219
|
-
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
220
|
-
const { user, loginStatus, isLoading, openRenown } = useUser()
|
|
221
|
-
const router = useRouter()
|
|
222
|
-
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
if (!isLoading && loginStatus !== 'authorized') {
|
|
225
|
-
// Redirect to login or show login prompt
|
|
226
|
-
router.push('/login')
|
|
227
|
-
}
|
|
228
|
-
}, [isLoading, loginStatus, router])
|
|
229
|
-
|
|
230
|
-
if (isLoading) {
|
|
231
|
-
return <div>Loading...</div>
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (loginStatus !== 'authorized' || !user) {
|
|
235
|
-
return (
|
|
236
|
-
<div className="flex flex-col items-center justify-center min-h-screen">
|
|
237
|
-
<h1>Authentication Required</h1>
|
|
238
|
-
<p>Please log in to access this page</p>
|
|
239
|
-
<button onClick={openRenown}>Login</button>
|
|
240
|
-
</div>
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return <>{children}</>
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### User Profile Display
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
'use client'
|
|
252
|
-
|
|
253
|
-
import { useUser } from '@renown/sdk'
|
|
254
|
-
|
|
255
|
-
export function UserProfile() {
|
|
256
|
-
const { user, logout } = useUser()
|
|
257
|
-
|
|
258
|
-
if (!user) return null
|
|
259
|
-
|
|
260
|
-
return (
|
|
261
|
-
<div className="profile-card">
|
|
262
|
-
<header>
|
|
263
|
-
{user.avatar && (
|
|
264
|
-
<img
|
|
265
|
-
src={user.avatar}
|
|
266
|
-
alt={user.name || 'User avatar'}
|
|
267
|
-
className="avatar"
|
|
268
|
-
/>
|
|
269
|
-
)}
|
|
270
|
-
<h2>{user.name || 'Anonymous User'}</h2>
|
|
271
|
-
</header>
|
|
272
|
-
|
|
273
|
-
<dl className="profile-details">
|
|
274
|
-
<dt>DID</dt>
|
|
275
|
-
<dd>{user.did}</dd>
|
|
276
|
-
|
|
277
|
-
{user.ethAddress && (
|
|
278
|
-
<>
|
|
279
|
-
<dt>Ethereum Address</dt>
|
|
280
|
-
<dd>{user.ethAddress}</dd>
|
|
281
|
-
</>
|
|
282
|
-
)}
|
|
283
|
-
|
|
284
|
-
{user.email && (
|
|
285
|
-
<>
|
|
286
|
-
<dt>Email</dt>
|
|
287
|
-
<dd>{user.email}</dd>
|
|
288
|
-
</>
|
|
289
|
-
)}
|
|
290
|
-
</dl>
|
|
291
|
-
|
|
292
|
-
<button onClick={logout}>Logout</button>
|
|
293
|
-
</div>
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## Advanced Patterns
|
|
299
|
-
|
|
300
|
-
### Conditional Navigation Based on Auth
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
'use client'
|
|
304
|
-
|
|
305
|
-
import { useUser } from '@renown/sdk'
|
|
306
|
-
import Link from 'next/link'
|
|
307
|
-
|
|
308
|
-
export function Navigation() {
|
|
309
|
-
const { user, loginStatus } = useUser()
|
|
310
|
-
|
|
311
|
-
const isAuthorized = loginStatus === 'authorized'
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<nav>
|
|
315
|
-
<Link href="/">Home</Link>
|
|
316
|
-
|
|
317
|
-
{isAuthorized ? (
|
|
318
|
-
<>
|
|
319
|
-
<Link href="/dashboard">Dashboard</Link>
|
|
320
|
-
<Link href="/profile">Profile</Link>
|
|
321
|
-
<Link href="/settings">Settings</Link>
|
|
322
|
-
</>
|
|
323
|
-
) : (
|
|
324
|
-
<>
|
|
325
|
-
<Link href="/about">About</Link>
|
|
326
|
-
<Link href="/pricing">Pricing</Link>
|
|
327
|
-
</>
|
|
328
|
-
)}
|
|
329
|
-
</nav>
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Auth State Listener
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
'use client'
|
|
338
|
-
|
|
339
|
-
import { useUser } from '@renown/sdk'
|
|
340
|
-
import { useEffect } from 'react'
|
|
341
|
-
import { useRouter } from 'next/navigation'
|
|
342
|
-
import { toast } from 'react-hot-toast'
|
|
343
|
-
|
|
344
|
-
export function AuthStateListener() {
|
|
345
|
-
const { user, loginStatus } = useUser()
|
|
346
|
-
const router = useRouter()
|
|
347
|
-
|
|
348
|
-
useEffect(() => {
|
|
349
|
-
if (loginStatus === 'authorized' && user) {
|
|
350
|
-
// User just logged in
|
|
351
|
-
toast.success(`Welcome back, ${user.name || 'User'}!`)
|
|
352
|
-
|
|
353
|
-
// Track analytics
|
|
354
|
-
analytics.identify(user.did, {
|
|
355
|
-
name: user.name,
|
|
356
|
-
ethAddress: user.ethAddress,
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Redirect to dashboard
|
|
360
|
-
router.push('/dashboard')
|
|
361
|
-
}
|
|
362
|
-
}, [loginStatus, user, router])
|
|
363
|
-
|
|
364
|
-
useEffect(() => {
|
|
365
|
-
if (loginStatus === 'not-authorized') {
|
|
366
|
-
// User logged out
|
|
367
|
-
toast.info('You have been logged out')
|
|
368
|
-
|
|
369
|
-
// Clear analytics
|
|
370
|
-
analytics.reset()
|
|
371
|
-
|
|
372
|
-
// Redirect to home
|
|
373
|
-
router.push('/')
|
|
374
|
-
}
|
|
375
|
-
}, [loginStatus, router])
|
|
376
|
-
|
|
377
|
-
return null // This is a listener component
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### Custom Auth Hook with Additional Logic
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
'use client'
|
|
385
|
-
|
|
386
|
-
import { useUser as useRenownAuth } from '@renown/sdk'
|
|
387
|
-
import { useEffect, useState } from 'react'
|
|
388
|
-
|
|
389
|
-
interface ExtendedAuthState {
|
|
390
|
-
user: User | null
|
|
391
|
-
isAuthenticated: boolean
|
|
392
|
-
isLoading: boolean
|
|
393
|
-
hasCompletedProfile: boolean
|
|
394
|
-
login: () => void
|
|
395
|
-
logout: () => Promise<void>
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export function useUser(): ExtendedAuthState {
|
|
399
|
-
const auth = useRenownAuth()
|
|
400
|
-
const [hasCompletedProfile, setHasCompletedProfile] = useState(false)
|
|
401
|
-
|
|
402
|
-
useEffect(() => {
|
|
403
|
-
if (auth.user) {
|
|
404
|
-
// Check if user has completed their profile
|
|
405
|
-
const isComplete = !!(
|
|
406
|
-
auth.user.name &&
|
|
407
|
-
auth.user.avatar &&
|
|
408
|
-
auth.user.email
|
|
409
|
-
)
|
|
410
|
-
setHasCompletedProfile(isComplete)
|
|
411
|
-
} else {
|
|
412
|
-
setHasCompletedProfile(false)
|
|
413
|
-
}
|
|
414
|
-
}, [auth.user])
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
user: auth.user,
|
|
418
|
-
isAuthenticated: auth.loginStatus === 'authorized',
|
|
419
|
-
isLoading: auth.isLoading,
|
|
420
|
-
hasCompletedProfile,
|
|
421
|
-
login: auth.openRenown,
|
|
422
|
-
logout: auth.logout,
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Role-Based Access Control
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
'use client'
|
|
431
|
-
|
|
432
|
-
import { useUser } from '@renown/sdk'
|
|
433
|
-
import { ReactNode } from 'react'
|
|
434
|
-
|
|
435
|
-
interface RBACProps {
|
|
436
|
-
children: ReactNode
|
|
437
|
-
allowedRoles?: string[]
|
|
438
|
-
fallback?: ReactNode
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export function RoleBasedAccess({
|
|
442
|
-
children,
|
|
443
|
-
allowedRoles = [],
|
|
444
|
-
fallback = <div>Access Denied</div>
|
|
445
|
-
}: RBACProps) {
|
|
446
|
-
const { user, loginStatus } = useUser()
|
|
447
|
-
|
|
448
|
-
if (loginStatus !== 'authorized' || !user) {
|
|
449
|
-
return fallback
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Check user roles (you'd fetch this from your backend)
|
|
453
|
-
const userRoles = getUserRoles(user.did) // Implement this
|
|
454
|
-
|
|
455
|
-
const hasAccess = allowedRoles.length === 0 ||
|
|
456
|
-
allowedRoles.some(role => userRoles.includes(role))
|
|
457
|
-
|
|
458
|
-
return hasAccess ? <>{children}</> : fallback
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Usage
|
|
462
|
-
function AdminPanel() {
|
|
463
|
-
return (
|
|
464
|
-
<RoleBasedAccess allowedRoles={['admin', 'moderator']}>
|
|
465
|
-
<h1>Admin Panel</h1>
|
|
466
|
-
{/* Admin content */}
|
|
467
|
-
</RoleBasedAccess>
|
|
468
|
-
)
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
## Security Considerations
|
|
473
|
-
|
|
474
|
-
### 1. Token Storage
|
|
475
|
-
|
|
476
|
-
The SDK stores session data in `sessionStorage` (not `localStorage`) for security:
|
|
477
|
-
|
|
478
|
-
- ✅ Session data clears when tab closes
|
|
479
|
-
- ✅ Not accessible across tabs
|
|
480
|
-
- ✅ Not persisted across browser sessions
|
|
481
|
-
- ✅ Protected from XSS via httpOnly (for API tokens)
|
|
482
|
-
|
|
483
|
-
### 2. DID Validation
|
|
484
|
-
|
|
485
|
-
Always validate DIDs before processing:
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
function isValidDID(did: string): boolean {
|
|
489
|
-
// Must start with did:pkh:
|
|
490
|
-
if (!did.startsWith('did:pkh:')) return false
|
|
491
|
-
|
|
492
|
-
// Must have correct number of parts
|
|
493
|
-
const parts = did.split(':')
|
|
494
|
-
if (parts.length !== 5) return false
|
|
495
|
-
|
|
496
|
-
// Validate ethereum address format
|
|
497
|
-
const address = parts[4]
|
|
498
|
-
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) return false
|
|
499
|
-
|
|
500
|
-
return true
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### 3. Secure Communication
|
|
505
|
-
|
|
506
|
-
Always use HTTPS for Renown URLs:
|
|
507
|
-
|
|
508
|
-
```typescript
|
|
509
|
-
const RENOWN_URL = process.env.NEXT_PUBLIC_RENOWN_URL
|
|
510
|
-
|
|
511
|
-
if (RENOWN_URL && !RENOWN_URL.startsWith('https://')) {
|
|
512
|
-
console.warn('WARNING: Renown URL should use HTTPS')
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### 4. Session Timeout
|
|
517
|
-
|
|
518
|
-
Implement session timeout for security:
|
|
519
|
-
|
|
520
|
-
```typescript
|
|
521
|
-
const SESSION_TIMEOUT = 24 * 60 * 60 * 1000 // 24 hours
|
|
522
|
-
|
|
523
|
-
function isSessionValid(timestamp: number): boolean {
|
|
524
|
-
const now = Date.now()
|
|
525
|
-
const age = now - timestamp
|
|
526
|
-
return age < SESSION_TIMEOUT
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### 5. Server-Side Verification
|
|
531
|
-
|
|
532
|
-
**Never trust client-side auth alone**. Always verify on the server:
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
// Server-side API route
|
|
536
|
-
export async function GET(request: Request) {
|
|
537
|
-
const authHeader = request.headers.get('authorization')
|
|
538
|
-
|
|
539
|
-
if (!authHeader) {
|
|
540
|
-
return new Response('Unauthorized', { status: 401 })
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Verify the JWT/credential with Renown
|
|
544
|
-
const isValid = await verifyRenownCredential(authHeader)
|
|
545
|
-
|
|
546
|
-
if (!isValid) {
|
|
547
|
-
return new Response('Invalid credentials', { status: 403 })
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Proceed with authorized request
|
|
551
|
-
return Response.json({ data: 'Protected data' })
|
|
552
|
-
}
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
## Best Practices
|
|
556
|
-
|
|
557
|
-
### 1. Handle Loading States
|
|
558
|
-
|
|
559
|
-
Always handle loading states to provide good UX:
|
|
560
|
-
|
|
561
|
-
```typescript
|
|
562
|
-
function MyComponent() {
|
|
563
|
-
const { user, isLoading, loginStatus } = useUser()
|
|
564
|
-
|
|
565
|
-
if (isLoading) {
|
|
566
|
-
return <Skeleton /> // Show skeleton/spinner
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Now safe to use user/loginStatus
|
|
570
|
-
}
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
### 2. Graceful Degradation
|
|
574
|
-
|
|
575
|
-
Provide fallbacks for unauthenticated users:
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
function FeatureSection() {
|
|
579
|
-
const { user } = useUser()
|
|
580
|
-
|
|
581
|
-
return (
|
|
582
|
-
<section>
|
|
583
|
-
{user ? (
|
|
584
|
-
<PersonalizedContent user={user} />
|
|
585
|
-
) : (
|
|
586
|
-
<GenericContent />
|
|
587
|
-
)}
|
|
588
|
-
</section>
|
|
589
|
-
)
|
|
590
|
-
}
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### 3. Cleanup on Unmount
|
|
594
|
-
|
|
595
|
-
Clean up subscriptions and listeners:
|
|
596
|
-
|
|
597
|
-
```typescript
|
|
598
|
-
useEffect(() => {
|
|
599
|
-
const handleAuthChange = () => {
|
|
600
|
-
// Handle auth changes
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Subscribe to auth events
|
|
604
|
-
const unsubscribe = subscribeToAuthEvents(handleAuthChange)
|
|
605
|
-
|
|
606
|
-
return () => {
|
|
607
|
-
unsubscribe() // Cleanup
|
|
608
|
-
}
|
|
609
|
-
}, [])
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### 4. Error Boundaries
|
|
613
|
-
|
|
614
|
-
Wrap auth components in error boundaries:
|
|
615
|
-
|
|
616
|
-
```typescript
|
|
617
|
-
<ErrorBoundary fallback={<AuthError />}>
|
|
618
|
-
<RenownUserProvider>
|
|
619
|
-
<App />
|
|
620
|
-
</RenownUserProvider>
|
|
621
|
-
</ErrorBoundary>
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
## Troubleshooting
|
|
625
|
-
|
|
626
|
-
### Issue: Auth state not updating
|
|
627
|
-
|
|
628
|
-
**Cause:** Component not re-rendering when auth changes
|
|
629
|
-
|
|
630
|
-
**Solution:** Ensure you're using `useUser()` hook, not accessing `window.renown` directly
|
|
631
|
-
|
|
632
|
-
```typescript
|
|
633
|
-
// ❌ Wrong
|
|
634
|
-
const user = window.renown?.user
|
|
635
|
-
|
|
636
|
-
// ✅ Correct
|
|
637
|
-
const { user } = useUser()
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Issue: Session not persisting
|
|
641
|
-
|
|
642
|
-
**Cause:** SessionStorage might be disabled or cleared
|
|
643
|
-
|
|
644
|
-
**Solution:** Check browser settings and handle gracefully
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
try {
|
|
648
|
-
SessionStorageManager.setUserData(data)
|
|
649
|
-
} catch (error) {
|
|
650
|
-
console.warn('SessionStorage not available:', error)
|
|
651
|
-
// Fallback to in-memory storage
|
|
652
|
-
}
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### Issue: Multiple login popups
|
|
656
|
-
|
|
657
|
-
**Cause:** `openRenown()` called multiple times
|
|
658
|
-
|
|
659
|
-
**Solution:** Debounce the login button
|
|
660
|
-
|
|
661
|
-
```typescript
|
|
662
|
-
const handleLogin = useCallback(
|
|
663
|
-
debounce(() => {
|
|
664
|
-
openRenown()
|
|
665
|
-
}, 1000),
|
|
666
|
-
[openRenown]
|
|
667
|
-
)
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
## Next Steps
|
|
671
|
-
|
|
672
|
-
- Read the [API Reference](./02-APIReference.md) for detailed documentation
|