@netlify/agent-runner-cli 1.75.0-alpha.0 → 1.76.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/dist/bin-local.js +37 -43
- package/dist/bin.js +48 -54
- package/dist/index.js +41 -47
- package/dist/skills/general-database/SKILL.md +26 -0
- package/dist/skills/netlify-identity/SKILL.md +329 -0
- package/package.json +1 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netlify-identity
|
|
3
|
+
description: Add user authentication with Netlify Identity. Use when implementing signups, logins, password recovery, role-based access control, OAuth providers, or protecting Netlify Functions with user tokens.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Netlify Identity
|
|
7
|
+
|
|
8
|
+
Netlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based
|
|
9
|
+
access control. It is built on [GoTrue](https://github.com/netlify/gotrue) and issues JSON Web Tokens (JWTs).
|
|
10
|
+
|
|
11
|
+
There are two integration paths:
|
|
12
|
+
|
|
13
|
+
- **Widget** (`netlify-identity-widget`) — Drop-in UI modal for login, signup, and password recovery. Zero framework
|
|
14
|
+
dependencies. Best when you want auth working quickly without building custom forms.
|
|
15
|
+
- **gotrue-js** — Programmatic client for full control over authentication flows. Best when you need custom UI or
|
|
16
|
+
headless auth logic.
|
|
17
|
+
|
|
18
|
+
Both communicate with the Identity endpoint at `/.netlify/identity` on your site.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
Add the widget CDN script and a menu element for instant auth:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
|
26
|
+
<div data-netlify-identity-menu></div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Handle logins:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
netlifyIdentity.on('login', async (user) => {
|
|
33
|
+
const jwt = await netlifyIdentity.refresh()
|
|
34
|
+
await fetch('/.netlify/functions/protected', {
|
|
35
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### When to Use Widget vs gotrue-js
|
|
41
|
+
|
|
42
|
+
- **Prefer the widget** when the user wants auth working quickly, doesn't have custom UI requirements, or is building a
|
|
43
|
+
simple site. It requires minimal code and automatically sets the `nf_jwt` cookie needed for role-based redirects.
|
|
44
|
+
- **Use gotrue-js** when the user needs custom login/signup forms, headless auth, or programmatic control over the auth
|
|
45
|
+
flow. Pass `setCookie: true` if you need role-based redirects (see below).
|
|
46
|
+
- **Never mix both** in the same page unless accessing `netlifyIdentity.gotrue` for low-level operations while using the
|
|
47
|
+
widget for UI.
|
|
48
|
+
|
|
49
|
+
## Setup
|
|
50
|
+
|
|
51
|
+
Identity is automatically enabled when the deploy includes Identity code. Default settings:
|
|
52
|
+
|
|
53
|
+
- **Registration** — Open (anyone can sign up). Change to Invite only in **Project configuration > Identity** if needed.
|
|
54
|
+
- **Autoconfirm** — Off (new signups require email confirmation). Enable in **Project configuration > Identity** to skip confirmation during development.
|
|
55
|
+
|
|
56
|
+
## Widget (Drop-in UI)
|
|
57
|
+
|
|
58
|
+
### CDN Script Tag
|
|
59
|
+
|
|
60
|
+
Include the widget script and add HTML attributes for automatic controls:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
|
64
|
+
|
|
65
|
+
<!-- Login/Signup menu (shows username + logout when logged in) -->
|
|
66
|
+
<div data-netlify-identity-menu></div>
|
|
67
|
+
|
|
68
|
+
<!-- Simple button that opens the modal -->
|
|
69
|
+
<div data-netlify-identity-button>Login with Netlify Identity</div>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The widget attaches itself to `window.netlifyIdentity` automatically.
|
|
73
|
+
|
|
74
|
+
### npm Module
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install netlify-identity-widget
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import netlifyIdentity from 'netlify-identity-widget'
|
|
82
|
+
|
|
83
|
+
netlifyIdentity.init({
|
|
84
|
+
container: '#netlify-modal', // defaults to document.body
|
|
85
|
+
locale: 'en', // language code (en, fr, es, pt, de, etc.)
|
|
86
|
+
// APIUrl: 'https://your-site.netlify.app/.netlify/identity', // only for non-Netlify hosts
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Set `APIUrl` only when the app is served from a different domain than the Identity endpoint (Cordova, Electron, or
|
|
91
|
+
custom domains).
|
|
92
|
+
|
|
93
|
+
### Events
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
netlifyIdentity.on('init', (user) => console.log('init', user))
|
|
97
|
+
netlifyIdentity.on('login', (user) => console.log('login', user))
|
|
98
|
+
netlifyIdentity.on('logout', () => console.log('Logged out'))
|
|
99
|
+
netlifyIdentity.on('error', (err) => console.error('Error', err))
|
|
100
|
+
|
|
101
|
+
netlifyIdentity.off('login') // unbind all login handlers
|
|
102
|
+
netlifyIdentity.off('login', handler) // unbind a specific handler
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Key Methods
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
netlifyIdentity.open() // open modal (default tab)
|
|
109
|
+
netlifyIdentity.open('login') // open to login tab
|
|
110
|
+
netlifyIdentity.open('signup') // open to signup tab
|
|
111
|
+
netlifyIdentity.open('signup', { full_name: 'Jane' }) // pre-fill signup metadata
|
|
112
|
+
netlifyIdentity.close()
|
|
113
|
+
netlifyIdentity.logout()
|
|
114
|
+
const user = netlifyIdentity.currentUser() // null if not logged in
|
|
115
|
+
const jwt = await netlifyIdentity.refresh() // refresh the JWT
|
|
116
|
+
const gotrueClient = netlifyIdentity.gotrue // underlying GoTrue client
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## gotrue-js (Programmatic Client)
|
|
120
|
+
|
|
121
|
+
### Install
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install gotrue-js
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Initialize
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import GoTrue from 'gotrue-js'
|
|
131
|
+
|
|
132
|
+
const auth = new GoTrue({
|
|
133
|
+
APIUrl: 'https://<your-site>.netlify.app/.netlify/identity',
|
|
134
|
+
setCookie: false,
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`setCookie: true` tells the server to set HttpOnly cookies, which protects tokens from XSS but is required if you need
|
|
139
|
+
role-based redirects without the widget (the CDN reads an `nf_jwt` cookie to evaluate redirect conditions). Use `false`
|
|
140
|
+
when you manage tokens entirely in JavaScript and don't need CDN-level access control.
|
|
141
|
+
|
|
142
|
+
### Core Methods
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
// Signup (sends confirmation email if autoconfirm is off)
|
|
146
|
+
const response = await auth.signup(email, password)
|
|
147
|
+
// Pass user_metadata during signup: auth.signup(email, password, { full_name: 'Jane' })
|
|
148
|
+
|
|
149
|
+
// Confirm email (token from confirmation email URL fragment)
|
|
150
|
+
const user = await auth.confirm(token, true) // remember = persist session
|
|
151
|
+
|
|
152
|
+
// Login
|
|
153
|
+
const user = await auth.login(email, password, true) // remember = persist session
|
|
154
|
+
|
|
155
|
+
// Current user (from localStorage)
|
|
156
|
+
const user = auth.currentUser()
|
|
157
|
+
|
|
158
|
+
// Get fresh JWT (auto-refreshes if expired; pass true to force)
|
|
159
|
+
const token = await user.jwt()
|
|
160
|
+
|
|
161
|
+
// Update user
|
|
162
|
+
await user.update({ email: 'new@example.com', password: 'newpassword' })
|
|
163
|
+
await user.update({ data: { full_name: 'Jane Doe' } }) // update user_metadata
|
|
164
|
+
|
|
165
|
+
// Logout
|
|
166
|
+
await user.logout()
|
|
167
|
+
|
|
168
|
+
// Password recovery
|
|
169
|
+
await auth.requestPasswordRecovery(email) // sends recovery email
|
|
170
|
+
const user = await auth.recover(recoveryToken, true) // remember = persist session
|
|
171
|
+
|
|
172
|
+
// Check server settings (signup enabled? autoconfirm? OAuth providers?)
|
|
173
|
+
const settings = await auth.settings()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Error Handling
|
|
177
|
+
|
|
178
|
+
gotrue-js exports `JSONHTTPError`, `TextHTTPError`, and `HTTPError` for granular error catching. Use `error.json`,
|
|
179
|
+
`error.data`, or `error.status` respectively.
|
|
180
|
+
|
|
181
|
+
## Token Handling
|
|
182
|
+
|
|
183
|
+
- Access tokens expire after **1 hour**
|
|
184
|
+
- Both `user.jwt()` (gotrue-js) and `netlifyIdentity.refresh()` (widget) auto-refresh using the refresh token
|
|
185
|
+
- Always call `jwt()` or `refresh()` before authenticated requests — do not cache tokens
|
|
186
|
+
- Send the token as `Authorization: Bearer <token>`
|
|
187
|
+
|
|
188
|
+
## Roles and Authorization
|
|
189
|
+
|
|
190
|
+
### Metadata Types
|
|
191
|
+
|
|
192
|
+
- **`app_metadata.roles`** — Server-controlled. Only settable via the Netlify UI, admin API, or server-side code
|
|
193
|
+
(Identity event functions, protected functions with admin token). Do not allow users to set their own roles.
|
|
194
|
+
- **`user_metadata`** — User-controlled. Users can update this via `user.update({ data: { ... } })`.
|
|
195
|
+
|
|
196
|
+
### Role-Based Redirects
|
|
197
|
+
|
|
198
|
+
Use `netlify.toml` to restrict paths by role:
|
|
199
|
+
|
|
200
|
+
```toml
|
|
201
|
+
[[redirects]]
|
|
202
|
+
from = "/admin/*"
|
|
203
|
+
to = "/admin/:splat"
|
|
204
|
+
status = 200
|
|
205
|
+
conditions = { Role = ["admin"] }
|
|
206
|
+
|
|
207
|
+
[[redirects]]
|
|
208
|
+
from = "/admin/*"
|
|
209
|
+
to = "/"
|
|
210
|
+
status = 302
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Rules are evaluated top-to-bottom. The first redirect matches users with the `admin` role; everyone else falls through
|
|
214
|
+
to the second rule and is redirected away.
|
|
215
|
+
|
|
216
|
+
**How it works:** The widget sets an `nf_jwt` cookie that the CDN reads to evaluate `conditions = { Role = [...] }`.
|
|
217
|
+
Without this cookie, role-based redirects will not work. If using gotrue-js instead of the widget, pass
|
|
218
|
+
`setCookie: true` when initializing GoTrue so the server sets this cookie.
|
|
219
|
+
|
|
220
|
+
## Identity Event Functions
|
|
221
|
+
|
|
222
|
+
Special serverless functions that trigger on Identity lifecycle events. These use the **legacy named `handler` export**
|
|
223
|
+
(not the modern default export) because they receive `event.body` containing the user payload.
|
|
224
|
+
|
|
225
|
+
**Event names:** `identity-validate`, `identity-signup`, `identity-login`
|
|
226
|
+
|
|
227
|
+
- `identity-signup` — fires when a new user signs up (email/password or OAuth)
|
|
228
|
+
- `identity-login` — fires on each login
|
|
229
|
+
- `identity-validate` — fires during signup before the user is created; return a non-200 status to reject the signup
|
|
230
|
+
|
|
231
|
+
The filename must match the event name exactly (e.g. `netlify/functions/identity-signup.mts`).
|
|
232
|
+
|
|
233
|
+
### Example: Assign Default Role on Signup
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// netlify/functions/identity-signup.mts
|
|
237
|
+
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
|
|
238
|
+
|
|
239
|
+
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
|
|
240
|
+
const { user } = JSON.parse(event.body || '{}')
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
statusCode: 200,
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
app_metadata: {
|
|
246
|
+
...user.app_metadata,
|
|
247
|
+
roles: ['member'],
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export { handler }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The response body replaces `app_metadata` and/or `user_metadata` on the user record — include all fields you want to
|
|
257
|
+
keep, not just new ones.
|
|
258
|
+
|
|
259
|
+
## Protected Functions
|
|
260
|
+
|
|
261
|
+
Functions that verify the calling user's identity. Use the legacy `handler` export to access `context.clientContext`.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// netlify/functions/protected-action.mts
|
|
265
|
+
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
|
|
266
|
+
|
|
267
|
+
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
|
|
268
|
+
const { identity, user } = context.clientContext || {}
|
|
269
|
+
|
|
270
|
+
if (!user) {
|
|
271
|
+
return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// user.sub — user ID
|
|
275
|
+
// user.email — email address
|
|
276
|
+
// user.app_metadata.roles — assigned roles
|
|
277
|
+
// user.user_metadata — user-controlled data
|
|
278
|
+
|
|
279
|
+
// Use identity.token (admin token) to call the Identity admin API:
|
|
280
|
+
const adminAuthHeader = `Bearer ${identity.token}`
|
|
281
|
+
const response = await fetch(`${identity.url}/admin/users/${user.sub}`, {
|
|
282
|
+
method: 'PUT',
|
|
283
|
+
headers: { Authorization: adminAuthHeader },
|
|
284
|
+
body: JSON.stringify({ app_metadata: { roles: ['editor'] } }),
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const data = await response.json()
|
|
288
|
+
return { statusCode: 200, body: JSON.stringify(data) }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export { handler }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Note:** Operations using `identity.token` (admin token) do **not** work locally with `netlify dev`. Deploy to Netlify
|
|
295
|
+
to test server-side admin operations.
|
|
296
|
+
|
|
297
|
+
## External OAuth Providers
|
|
298
|
+
|
|
299
|
+
Netlify Identity supports Google, GitHub, GitLab, and BitBucket as built-in OAuth providers.
|
|
300
|
+
|
|
301
|
+
**Enable in Netlify UI:** Project configuration > Identity > Registration > External providers
|
|
302
|
+
|
|
303
|
+
### Widget
|
|
304
|
+
|
|
305
|
+
Once enabled in the UI, the widget automatically shows OAuth provider buttons. No additional code needed.
|
|
306
|
+
|
|
307
|
+
### gotrue-js
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
const url = auth.loginExternalUrl('github')
|
|
311
|
+
// Redirect the user to this URL to start the OAuth flow
|
|
312
|
+
window.location.href = url
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Available provider names: `'google'`, `'github'`, `'gitlab'`, `'bitbucket'`
|
|
316
|
+
|
|
317
|
+
## Common Errors & Solutions
|
|
318
|
+
|
|
319
|
+
- **"Signups not allowed for this instance" (403)** — Registration is set to Invite only. Change to Open in Project
|
|
320
|
+
configuration > Identity, or invite users from the Identity tab.
|
|
321
|
+
- **"Token expired" / 401 on API calls** — Stale access token. Always call `user.jwt()` or `netlifyIdentity.refresh()`
|
|
322
|
+
before authenticated requests.
|
|
323
|
+
- **Identity event function not triggering** — Verify filename matches exactly (`identity-signup`, `identity-validate`,
|
|
324
|
+
or `identity-login`), is in `netlify/functions/`, and uses `.mts`/`.mjs`.
|
|
325
|
+
- **`clientContext` is undefined** — Use named `handler` export (not `export default`), send `Authorization: Bearer`
|
|
326
|
+
header, and verify token is fresh.
|
|
327
|
+
- **Admin methods not working locally** — `identity.token` is unavailable in local dev. Deploy to Netlify to test.
|
|
328
|
+
- **"User not found" after OAuth login** — Enable the OAuth provider in Project configuration > Identity > External
|
|
329
|
+
providers. Users are created on first OAuth login.
|