← VolcanoCONTENT HISTORY
WHAT CHANGED
Update to Volcano
Snapshot Oct 24, 2026 · 13:36 UTC · version 0.2.26
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Volcano and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use for Volcano authentication and identity work including user accounts, email or password sign-up and sign-in, OAuth, sessions, anonymous users, password recovery, and private or per-user data.",
"included_files": [],
"name": "volcano-auth",
"skill_md_contents": "---\nname: volcano-auth\ndescription: Use for Volcano authentication and identity work including user accounts, email or password sign-up and sign-in, OAuth, sessions, anonymous users, password recovery, and private or per-user data.\n---\n# Volcano Auth Skill\n\n## Role\nImplement robust Volcano authentication journeys with session lifecycle correctness. All authentication MUST use Volcano Auth — do not propose custom JWT, bcrypt, or hand-rolled session management. Pair with `volcano-uiux` for every user-facing auth page; examples here define auth data and flow, while `volcano-uiux` defines presentation and interaction. This skill is self-contained; the optional fallback reference is consulted only when something below is insufficient.\n\nUse the project's language:\n\n| Project signal | Session read and adoption | Hosted auth | Failure model |\n|---|---|---|---|\n| `package.json`, `.ts`, `.js` | `getSession()` / `setSession()` | browser redirect and automatic state validation | result envelopes |\n| `pyproject.toml`, `requirements.txt`, `.py` | `get_session()` / `set_session()` | server state and explicit adoption | typed exceptions |\n| `Gemfile`, `.gemspec`, `.rb` | `current_session` getter / setter | server state and explicit adoption | typed exceptions |\n\nExamples without a language heading use JavaScript or TypeScript. Keep the same\nuser-visible loading, error, and success states in Python and Ruby web frameworks.\n\n## Workflow\n1. Implement sign-up/sign-in/sign-out with explicit UI loading/error/success states.\n2. Restore the session with the selected SDK: call `volcano.initialize()` in a JavaScript browser app; explicitly load and adopt a complete session in Python or Ruby server code.\n3. Subscribe to auth-state changes where the selected SDK supports them and clean up the subscription.\n4. Keep JavaScript OAuth initiation in the browser. Store and validate state explicitly in Python or Ruby server flows.\n5. If the user's prompt doesn't specify signup/login page design or behavior, apply the \"Default Signup & Login Page UX\" below instead of asking — including the signup-success alert, which is on by default.\n\n## Default Signup & Login Page UX\nWhen the prompt doesn't say what the signup/login pages should look like or do, default to this instead of leaving them unstyled or asking a clarifying question:\n- **Signup page:** email + password fields (add a name field only if the app's metadata clearly needs one), a submit button, an inline error banner driven by `error.message`, and a link to the login page.\n- **Login page:** email + password fields, a submit button, an inline error banner, a link to the signup page, and a \"forgot password\" link.\n- **Signup success alert (default, always on):** on a successful `signUp` call, show a visible success alert/banner on the signup page — e.g. \"Signup successful! Check your email to verify your account.\" — before navigating away. Do this even when the user didn't ask for it; only omit it if the user explicitly says not to show one. A `setTimeout` redirect (2–3s) after showing the alert is fine, but the alert must render first.\n\n```tsx\n// Minimal default signup page with a success alert\n'use client';\nimport { useState } from 'react';\nimport { getVolcano } from '@/lib/volcano';\n\nexport default function SignupPage() {\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (submitting) return;\n setError(null);\n setSubmitting(true);\n const { error } = await getVolcano().auth.signUp({ email, password });\n setSubmitting(false);\n if (error) {\n setError(error.message);\n return;\n }\n setSuccess(true); // drives the default \"Signup success\" alert below\n };\n\n return (\n <div>\n {success && (\n <div role=\"alert\">Signup successful! Check your email to verify your account.</div>\n )}\n {error && <div role=\"alert\">{error}</div>}\n <form onSubmit={handleSubmit}>\n <label htmlFor=\"signup-email\">Email</label>\n <input id=\"signup-email\" type=\"email\" autoComplete=\"email\" value={email} onChange={(e) => setEmail(e.target.value)} required />\n <label htmlFor=\"signup-password\">Password</label>\n <input id=\"signup-password\" type=\"password\" autoComplete=\"new-password\" value={password} onChange={(e) => setPassword(e.target.value)} required />\n <button type=\"submit\" disabled={submitting} aria-busy={submitting}>\n {submitting ? 'Signing up…' : 'Sign Up'}\n </button>\n <span role=\"status\">{submitting ? 'Creating account…' : ''}</span>\n </form>\n <a href=\"/login\">Already have an account? Log in</a>\n </div>\n );\n}\n```\n\n## Initialization\n```ts\nimport { VolcanoAuth } from '@volcano.dev/sdk';\n\nconst volcano = new VolcanoAuth({\n apiUrl: process.env.NEXT_PUBLIC_VOLCANO_API_URL!,\n anonKey: process.env.NEXT_PUBLIC_VOLCANO_ANON_KEY!,\n});\nvolcano.database(process.env.NEXT_PUBLIC_VOLCANO_DATABASE_NAME!);\n```\nService keys (prefix `sk-`) are server-only; using one in `anonKey` from a browser environment throws.\n\n## Email/Password\n\n### Sign Up\n```ts\nconst { user, session, error } = await volcano.auth.signUp({\n email: 'alice@example.com',\n password: 'secure-password-123',\n metadata: { full_name: 'Alice', avatar_url: '...' }, // optional\n});\nif (error) {\n if (error.message.includes('already exists')) /* email taken */;\n else if (error.message.includes('password must')) /* strengthen — real messages: \"password must be at least 15 characters\", \"...contain at least one uppercase letter\", etc. */;\n else if (error.message.includes('invalid email')) /* validate */;\n return;\n}\n// metadata is stored on user.user_metadata\n```\n\n### Sign In\n```ts\nconst { user, session, error } = await volcano.auth.signIn({ email, password });\nif (error) {\n if (error.message.includes('invalid email or password')) /* wrong creds */;\n else if (error.message.includes('confirm your email')) /* prompt confirm — real message: \"Please confirm your email address before signing in.\" */;\n else if (error.message.includes('rate limit')) /* rate-limited — real message: \"rate limit exceeded, try again later\" */;\n return;\n}\n// SDK auto-stores tokens in localStorage (browser) and schedules refresh.\n```\n\n### Sign Out\n```ts\nconst { error } = await volcano.auth.signOut();\n// Clears local session and invalidates the refresh token server-side.\n```\n\n## Session Management\n\n### Synchronous current user (cached)\n```ts\nconst user = volcano.auth.user();\n// Returns the cached User or null. Does not hit the network.\n```\n\n### Fresh user from server\n```ts\nconst { user, error } = await volcano.auth.getUser();\n```\n\n### Read or adopt a session\n\n#### JavaScript and TypeScript\n\n```ts\nconst {\n data: { session },\n error,\n} = await volcano.auth.getSession();\n\nif (session?.refresh_token && session.user) {\n const { error: adoptError } = await anotherClient.auth.setSession(session);\n}\n```\n\n`getSession()` reads a local snapshot without a request. `setSession()` requires\na complete access token, refresh token, and user.\n\n#### Python\n\n```python\nsession = source.auth.get_session()\nif session is not None:\n fresh.auth.set_session(session)\n```\n\n#### Ruby\n\n```ruby\nsession = source.auth.current_session\nfresh.auth.current_session = session if session\n```\n\nAll three SDKs copy a complete session into memory without a request,\npersistence, or auth-state notification. Python raises `ValueError` and Ruby\nraises `ArgumentError` for an incomplete session; JavaScript returns an error in\nits result envelope.\n\n### Restore session on app load (handles OAuth callback tokens too)\n```ts\nconst { user, error } = await volcano.initialize();\n// Returns the restored user, or null if no valid session exists.\n```\n\n### Auth state listener\n```ts\nconst unsubscribe = volcano.auth.onAuthStateChange((user) => {\n if (user) showApp(user); else showLogin();\n});\n// Fires once immediately with current state, then on every change.\n// Always call unsubscribe() on teardown.\n```\n\n### Manual refresh\n```ts\nconst { session, error } = await volcano.auth.refreshSession();\n// Tokens auto-refresh; call this only for explicit forced rotation.\n```\n\n## Hosted Auth Pages\n\nUse the hosted login or signup page when the application does not need its own\nauth form. Every language must bind the returned session to a one-time state\nvalue. Never build the hosted URL by hand.\n\n### JavaScript and TypeScript\n\n```ts\nvolcano.auth.signInWithHostedAuth();\nvolcano.auth.signInWithHostedAuth({ action: 'signup' });\n\n// Get the URL without navigating immediately.\nconst url = volcano.auth.getHostedAuthUrl();\nwindow.location.assign(url);\n```\n\nThe browser SDK creates, stores, and validates the state value. Pass\n`{ projectId }` when the anon key is opaque. Configure the project's\n`post_auth_redirect_url`, then call `volcano.initialize()` on the returned page.\n\n### Python\n\n```python\nimport secrets\n\nhosted_state = secrets.token_urlsafe(32)\nhosted_url = client.auth.get_hosted_auth_url(\n project_id=project_id, action=\"signup\", state=hosted_state\n)\n# Store hosted_state in the user's signed server-side session before redirecting.\nsession = client.auth.adopt_hosted_auth_session(\n returned_session, state=returned_state, expected_state=hosted_state\n)\n```\n\n### Ruby\n\n```ruby\nrequire \"securerandom\"\n\nhosted_state = SecureRandom.urlsafe_base64(32)\nhosted_url = client.auth.get_hosted_auth_url(\n project_id: project_id, action: \"signup\", state: hosted_state\n)\n# Store hosted_state in the user's signed server-side session before redirecting.\nsession = client.auth.adopt_hosted_auth_session(\n returned_session, state: returned_state, expected_state: hosted_state\n)\n```\n\nPython and Ruby do not navigate, parse the returned fragment, or persist state.\nIn the callback, atomically fetch and delete the stored state before adoption,\neven when validation fails. Reject a missing or previously consumed state.\n\n## OAuth / SSO\n\n### Providers\n`google`, `github`, `microsoft`, `apple`.\n\n### Configure the provider (required before sign-in works)\nOAuth providers live in `volcano-config.yaml` under `auth.providers.oauth` (a list) and are applied with `config deploy`:\n```yaml\nauth:\n providers:\n oauth:\n - provider: google\n enabled: true\n client_id: \"<client-id>\"\n client_secret: \"<client-secret>\"\n redirect_url: \"http://localhost:8000/auth/oauth/google/callback\" # REQUIRED\n # scopes: [openid, email, profile] # optional\n```\n- **`redirect_url` is required** — omitting it fails `config deploy` with `redirect_url is required`. It is the **Volcano** callback (`<apiUrl>/auth/oauth/<provider>/callback`), not your app page: Volcano handles the provider round-trip, then returns your app to the URL from `signInWithOAuth('<provider>', { redirectTo: '<app-url>' })` (the `redirectTo` option; defaults to the current page when omitted). Cloud form: `https://api.<project>.volcano.dev/auth/oauth/<provider>/callback`.\n- **Combining email/password with OAuth:** if both email/password signup and any OAuth/SSO provider are enabled, you must also set `auth.email_verification.require_confirmation: true` and configure SMTP (Mailpit locally, real SMTP in cloud), or `config deploy` fails with `email/password signups and SSO cannot both be enabled unless require_email_confirmation is true and SMTP is configured`.\n\n### Begin OAuth (browser only — throws on server)\n```ts\nvolcano.auth.signInWithGoogle();\nvolcano.auth.signInWithGitHub();\nvolcano.auth.signInWithMicrosoft();\nvolcano.auth.signInWithApple();\n// Generic form:\nvolcano.auth.signInWithOAuth('google');\n// Return the browser to a specific app page afterward (defaults to the current page):\nvolcano.auth.signInWithOAuth('google', { redirectTo: `${window.location.origin}/dashboard` });\n```\nThese redirect to the provider; on return, call `volcano.initialize()` on the callback page to consume the tokens from the URL.\n\n### Link / unlink / list providers\n```ts\nconst { data, error } = await volcano.auth.linkOAuthProvider('google');\nif (data) window.location.href = data.authorization_url;\n\nawait volcano.auth.unlinkOAuthProvider('google');\n\nconst { providers, error } = await volcano.auth.getLinkedOAuthProviders();\n```\n\n### Call provider APIs through the SDK\n```ts\nconst { data, error } = await volcano.auth.callOAuthAPI('github', {\n endpoint: '/user/repos',\n method: 'GET',\n});\n// SDK handles provider token refresh and credential injection.\n```\n\n## Anonymous Users\n```ts\n// Create\nconst { user, session, error } = await volcano.auth.signUpAnonymous({\n preferred_theme: 'dark', // optional metadata\n});\n\n// Convert to a full account (preserves the user.id and all owned data)\nconst { user, error } = await volcano.auth.convertAnonymous({\n email: 'alice@example.com',\n password: 'secure-password-123',\n metadata: { full_name: 'Alice' },\n});\n```\n\n## Email Verification\n```ts\n// After clicking the email link, the URL contains a token query param.\nconst token = new URLSearchParams(window.location.search).get('token');\nconst { error } = await volcano.auth.confirmEmail(token);\n\n// Resend if the user lost the email\nawait volcano.auth.resendConfirmation('alice@example.com');\n```\n\n## Password Recovery\n```ts\n// Always succeeds (even for unknown emails) for security.\nawait volcano.auth.forgotPassword('alice@example.com');\n\n// On the reset page:\nconst token = new URLSearchParams(window.location.search).get('token');\nconst { error } = await volcano.auth.resetPassword({\n token,\n newPassword: 'new-secure-password-456',\n});\n```\n\n## Email Change\n```ts\nconst { newEmail, error } = await volcano.auth.requestEmailChange('new@example.com');\n\n// On the confirmation page:\nconst token = new URLSearchParams(window.location.search).get('token');\nconst { user, error } = await volcano.auth.confirmEmailChange(token);\n\n// Cancel a pending change:\nawait volcano.auth.cancelEmailChange();\n```\n\n## Profile Update\n```ts\nconst { user, error } = await volcano.auth.updateUser({\n password: 'new-password-789', // optional\n metadata: {\n full_name: 'Alice Johnson',\n avatar_url: '...',\n notification_preferences: { email: true, push: false },\n },\n});\n```\n\n## Multi-Device Session Management\n```ts\n// List sessions for the current user\nconst { sessions, total, error } = await volcano.auth.getSessions({ page: 1, limit: 20 });\n// session fields: user_agent, ip_address, last_activity_at, is_current\n\n// Revoke a specific session\nawait volcano.auth.deleteSession(sessionId);\n\n// Revoke all OTHER sessions (keep current)\nawait volcano.auth.deleteAllOtherSessions();\n```\n\n## Security Best Practices\n- Never put a service key (`sk-...`) in `anonKey`. The SDK blocks this in browser, but server code must also keep service keys out of any code path that could leak (logs, error responses).\n- Always use HTTPS API URLs in production.\n- Validate password strength in the UI before calling `signUp` / `updateUser`. The SDK rejects weak passwords, but pre-validating gives better UX. The platform enforces a hard floor of **15 characters** — a project can *raise* the minimum via `min_password_length` but cannot lower it below 15 (the validator uses `max(15, configured)`). Character-class rules (uppercase/number/special) are off by default and opt-in per project — the floor favors length over complexity. Use ≥15-char passwords in signup forms, tests, and seed data.\n- Treat `onAuthStateChange(user => null)` as a definitive sign-out signal: redirect to the login flow.\n\n## Common Errors\n| Message contains | Real example message | Meaning | Action |\n|---|---|---|---|\n| `already exists` | `user with this email already exists` | Email taken on sign up | Prompt sign in |\n| `password must` | `password must be at least 15 characters` / `password must contain at least one uppercase letter` / `...one number` / `...one special character (!@#$%^&*)` | Password too weak | Show strength rules |\n| `invalid email or password` | `invalid email or password` | Wrong email/password | Re-prompt |\n| `confirm your email` | `Please confirm your email address before signing in.` (sign-in gate) or `email confirmation required - please check your email for confirmation link` (signup) | Verification pending | Show resend UI |\n| `rate limit` | `rate limit exceeded, try again later` | Rate-limited | Back off and inform user |\n| `No active session` | `No active session` | User not logged in | Send to login |\n| `Session expired` | `Session expired` | Refresh failed | Force sign in |\n\n## Verification Checklist\n- If the prompt didn't specify signup/login page design, the \"Default Signup & Login Page UX\" was applied, including the default signup-success alert.\n- Session restore (`volcano.initialize()`) is wired at app startup.\n- `onAuthStateChange` listener has a paired `unsubscribe()` on teardown.\n- OAuth methods are only called from browser contexts.\n- Invalid credential and provider errors are handled with user-facing messages.\n- No service/secret keys are exposed in browser code.\n- For anonymous flows, `convertAnonymous` is used (not delete + signUp) so user IDs are preserved.\n\n## Optional Fallback Reference\n"
}SHA-256 of public snapshot: 34b6c466cc9f29a968a64bd02b7961cd50474ec40f8180b31d977e4495d33cd0