โ Back to User Management
๐ Access Manager
User management, scoped access control, and multi-method authentication for scottfelten.com services.
v2.0 โ Live
18 Services
3 Auth Methods
March 2026
1 Overview
Access Manager is the identity and access layer for all scottfelten.com services.
It controls who can sign in, what they can access, and how they authenticate โ all from a single dashboard.
๐ต Google SSO
Sign in with Google. Primary method for team members and close collaborators. Powered by oauth2-proxy.
โจ Magic Link
One-time login link sent to email. No password needed. Great for clients and external partners.
๐ Email + Password
Traditional login with bcrypt hashing. 12-char minimum. Lockout protection. Password reset via email.
๐ก How it works: Every scottfelten.com service checks with Access Manager before letting anyone in.
When a user visits any service, Access Manager verifies their session and checks if they have permission for that specific service.
2 Adding a User
-
Click "+ Add User" on the dashboard
Opens the user creation modal with all configuration options.
-
Enter their email and name
Email is their login identifier. Must be unique across all users.
-
Choose their role
Client โ external user, limited access.
Partner โ business collaborator (e.g., Colum for IAG).
Admin โ full access to everything including this dashboard.
-
Select auth method(s)
Pick one or more: Google (they need a Google account), Magic Link (any email works),
or Password (you set their initial password, or they can set it later via reset).
-
Assign services
Check individual services they should access, or select All Services (*) for unrestricted access.
Services are auto-populated from the project inventory โ always current.
-
Optional: Add tags
Comma-separated labels for organization (e.g., "partner, bpo-expert"). Purely informational for now.
โก Quick example โ Adding Colum (IAG partner):
Email: colum@whatever.com ยท Role: Partner ยท Auth: Magic Link ยท Services: iag-website, iag-data-viewer, intellicert, usecasegen
3 Auth Methods โ How Each Works
๐ต Google SSO
The primary auth method. Uses oauth2-proxy to handle the Google OAuth flow. When a user visits a service:
- They're redirected to Google's sign-in page
- After authenticating, oauth2-proxy sets a session cookie
- Access Manager verifies the cookie and checks their service permissions
Requirement: User must have a Google account, and their email must be in the allowlist.
โจ Magic Link
Email-based, passwordless login. Perfect for clients and partners who don't use Google.
- User enters their email on the login page
- They receive a one-time link (expires in 15 minutes)
- Clicking the link sets a session cookie valid for 7 days
- No password to remember, no account to create
๐ Security: Links are single-use (consumed on first click) and time-limited. The token is a 32-byte random hex string โ not guessable.
๐ Email + Password
Traditional login for users who prefer (or need) a persistent credential.
- Passwords hashed with bcrypt (12 rounds) โ industry standard
- 12-character minimum โ enforced on creation and reset
- Lockout: 5 failed attempts โ account locked for 30 minutes (auto-unlocks)
- Password reset: sends a time-limited reset link (1 hour expiry)
Setting a Password
Two ways to set a password for a user:
- At creation: Enter a password in the "Initial Password" field when adding the user
- After creation: Click the ๐ button next to their name in the dashboard
Users can also reset their own password via the "Forgot password?" link on the login page.
4 Services & Permissions
Services are auto-discovered from the project inventory every 5 minutes.
When a new service is deployed, it appears in the services list automatically โ no manual updates needed.
How Scoping Works
- Each user has a
services[] array listing which services they can access
- When they visit a service, Access Manager maps the hostname to a service ID and checks if it's in their list
- Wildcard:
* grants access to everything (admin default)
Current Service Map
These services are protected by Access Manager. The list updates automatically from inventory.
| Service | URL | Category |
| inventory | inv.scottfelten.com | Core |
| access-manager | users.scottfelten.com | Core |
| taos | goalstack.scottfelten.com | Core |
| signal-tower | signal.scottfelten.com | Intelligence |
| customer-360 | c360.scottfelten.com | Sales |
| models-embeddings | models.scottfelten.com | AI/ML |
| iag-website | intelligenceadvisorygroup.com | IAG |
| iag-data-viewer | iag.scottfelten.com | IAG |
| intellicert | intellicert.app | IAG |
| usecasegen | app.usecasegen.app | IAG |
| + 8 more (auto-discovered from inventory) |
Manual Sync
Click the โป Sync button in the dashboard header to force a refresh from inventory. Normally happens automatically every 5 minutes.
5 The Login Page
Available at /login.html โ this is the public entry point for non-Google users.
It's accessible without authentication.
Three Tabs
- Google โ redirects to Google sign-in (for users with Google SSO enabled)
- Magic Link โ enter email, receive login link
- Password โ enter email + password, with "Forgot password?" reset flow
Redirect Flow
When a user hits a protected service without a session, they'll be redirected to the login page with a ?rd= parameter.
After successful auth, they're sent back to the service they originally wanted.
๐ก Currently: The default redirect for unauthenticated users goes to Google sign-in.
To send non-Google users to the multi-auth login page instead, share the direct link:
https://users.scottfelten.com/login.html?rd=https://SERVICE_URL
6 Security
| Feature | Detail |
| Password hashing | bcrypt, 12 rounds |
| Password minimum | 12 characters |
| Login lockout | 5 failed attempts โ 30-minute lock (auto-unlocks) |
| Magic link expiry | 15 minutes, single-use |
| Reset token expiry | 1 hour, single-use |
| Session duration | 7 days (JWT cookie on .scottfelten.com) |
| Cookie flags | httpOnly, secure, sameSite=lax |
| Token entropy | 32 bytes (256-bit) random hex |
| Email enumeration | Prevented โ all responses are identical regardless of email existence |
Session Types
Two session mechanisms coexist:
- oauth2-proxy cookie โ for Google SSO users (managed by oauth2-proxy)
- _am_session cookie โ for magic link and password users (JWT, managed by Access Manager)
The /auth/check endpoint tries the _am_session first, then falls back to oauth2-proxy. Either way, scoped access is enforced.
7 API Reference
All admin endpoints require authentication (Google SSO or _am_session with admin role).
Authentication
POST /auth/magic/request โ Request a magic link
Public endpoint. Sends a login link to the provided email (if registered).
POST /auth/magic/request
{ "email": "user@example.com", "redirect": "https://inv.scottfelten.com" }
{ "ok": true, "message": "If that email is registered, a login link has been sent." }
POST /auth/password/login โ Email + password login
Public endpoint. Returns session cookie on success.
POST /auth/password/login
{ "email": "user@example.com", "password": "their-password" }
{ "ok": true, "user": { "email": ..., "name": ..., "role": ... } }
{ "error": "Invalid email or password", "attemptsRemaining": 3 }
{ "error": "Account locked. Try again in 28 minutes.", "locked": true }
POST /auth/password/set โ Set password for a user (admin)
POST /auth/password/set
{ "email": "user@example.com", "password": "min-12-characters" }
POST /auth/password/reset-request โ Request password reset
Public endpoint. Sends a reset link to the email if registered.
POST /auth/password/reset-request
{ "email": "user@example.com" }
POST /auth/password/reset โ Execute password reset
POST /auth/password/reset
{ "token": "hex-token-from-email", "password": "new-password-12+" }
GET /auth/session โ Check current session
Public endpoint. Returns session info or { authenticated: false }.
POST /auth/logout โ Clear session
Clears the _am_session cookie. Note: does not clear the oauth2-proxy session (Google SSO).
User Management (Admin)
GET /api/users โ List all users
Returns array of users. Password hashes are excluded; includes hasPassword: true/false.
POST /api/users โ Create a user
POST /api/users
{
"email": "colum@example.com",
"name": "Colum",
"role": "partner",
"authMethods": ["magic-link"],
"services": ["iag-website", "iag-data-viewer", "intellicert", "usecasegen"],
"tags": ["partner"],
"password": "optional-initial-password"
}
PUT /api/users/:email โ Update a user
Partial update โ only send the fields you want to change.
PUT /api/users/colum@example.com
{ "services": ["iag-website", "intellicert"] }
DELETE /api/users/:email โ Remove a user
Removes the user and updates the oauth2-proxy allowlist. Cannot delete yourself.
Services
GET /api/services โ List available services
Returns sorted array of unique service IDs (deduplicated from hostname mappings).
GET /api/services/map โ Full hostname โ service map
Returns the complete mapping of hostnames to service IDs. Useful for debugging.
POST /api/services/sync โ Force sync from inventory
Pulls fresh data from inventory API. Normally happens automatically every 5 minutes.
Configuration
PUT /api/config/email โ Configure email transport
PUT /api/config/email
{
"host": "smtp.gmail.com",
"port": 587,
"user": "scott@scottfelten.com",
"pass": "google-app-password",
"from": "TARS <scott@scottfelten.com>"
}
8 Architecture
Auth Flow
โ
โ
โ no
โ no
โ yes (either check)
Infrastructure
| Component | Location | Port |
| Access Manager | VPS systemd: access-manager.service | 3030 |
| oauth2-proxy | VPS systemd: oauth2-proxy.service | 4180 |
| Inventory API | VPS systemd: inventory-api.service | 3025 |
| nginx | All services proxied, auth_request on each | 443 |
Data Files
| File | Purpose |
data/users.json | User store (email, role, services, passwordHash, authMethods) |
data/services-cache.json | Cached service map from inventory (refreshed every 5 min) |
data/tokens.json | Active magic link and reset tokens (auto-cleaned) |
data/lockouts.json | Failed login tracking and lockout state |
data/config.json | JWT secret, SMTP config, email settings |
/etc/oauth2-proxy-users.txt | oauth2-proxy email allowlist (auto-synced from users.json) |
9 Setting Up Email
Email is required for magic links and password resets. Without it, these features log to the server console
(useful for testing, not for real users).
Option A: Google Workspace SMTP (Recommended)
-
Create a Google App Password
-
Configure via API
PUT https://users.scottfelten.com/api/config/email
{
"host": "smtp.gmail.com",
"port": 587,
"user": "scott@scottfelten.com",
"pass": "xxxx-xxxx-xxxx-xxxx",
"from": "TARS <scott@scottfelten.com>"
}
-
Test it
Request a magic link for your own email. If it arrives, you're good.
Option B: Resend.com
Free tier: 100 emails/day. Use smtp.resend.com with your API key as the password.
10 Quick Reference
| Task | How |
| Add a Google SSO user | Dashboard โ + Add User โ select Google auth + services |
| Add a non-Google user | Dashboard โ + Add User โ select Magic Link or Password auth |
| Set/change a password | Dashboard โ click ๐ button next to user |
| See who has access to what | Dashboard โ services shown inline per user |
| Refresh the services list | Dashboard โ โป Sync button |
| Send a login link | Share: users.scottfelten.com/login.html |
| Check system health | GET /health โ shows users, services, email status |
| Unlock a locked account | Wait 30 min (auto-unlocks) or delete data/lockouts.json |
| Configure email | PUT /api/config/email (see Section 9) |
Access Manager v2.0 ยท Built by TARS ยท March 2026