# WyConnect - Sign in with Lee Wyatt Corp **WyConnect** is the one login for every Lee Wyatt Corp site and app: a drop-in "Sign in with Lee Wyatt Corp" button backed by the **Lee Wyatt Main Account** Supabase project (`cgwxlbenwaiowesagayo`) - the same identity used across `leewyattcorp.com`. Live base: `https://leewyattcorp.com/connect/` > The JS global is `WyConnect`. `LWCConnect` is kept as a backward-compatible alias. ## How it works ``` Your app --click--> wyconnect.js --popup--> leewyattcorp.com/connect --auth--> Supabase | onChange(session) <--postMessage(session, your-origin)--+ ``` 1. Your app loads `wyconnect.js` and renders the button. 2. On click, a popup opens on `leewyattcorp.com/connect/` (the user signs in with their LWC account). 3. The popup `postMessage`s the session back to **your exact origin** (never `*`), guarded by a random `state` nonce. 4. Your app gets `{ user, access_token, refresh_token, expires_at }`. Verify the token server-side before trusting it. This is the lightweight (Tier A) connector for Brandon's own products. A full OAuth2/OIDC provider (registered clients, consent screen, scoped tokens) can be layered on later in this same `/connect/` folder so WyConnect can become a global "Sign in with Lee Wyatt Corp" anyone can use. ## Front-end integration ```html
``` ### API | Method | Description | | --- | --- | | `WyConnect.init({ appId, appName, connectUrl? })` | Configure and bind the message listener. | | `WyConnect.renderButton(selector, { label?, compact?, onError? })` | Insert a branded button. | | `WyConnect.signIn()` | Open the popup; returns a Promise resolving to the session. | | `WyConnect.onChange(cb)` | Called with the session on sign in, and `null` on sign out. | | `WyConnect.getSession()` / `getUser()` / `getToken()` | Read the stored session/user/access token. | | `WyConnect.isSignedIn()` | Boolean. | | `WyConnect.signOut()` | Clear the local session in this app. | The session is stored in the app's own `localStorage` under `wyconnect-session`. ## Back-end verification Never trust the token from the browser alone - verify it with the LWC identity provider. Helpers are included (no dependencies, public anon key only): **Node 18+** ```js const { verifyLwcToken, requireLwcAuth } = require("./verify/verify-token"); // Manual: const user = await verifyLwcToken(req.headers.authorization); if (!user) return res.status(401).json({ error: "Sign in with Lee Wyatt Corp required" }); // Or as Express middleware: app.post("/api/thing", requireLwcAuth(), (req, res) => { res.json({ youAre: req.lwcUser.email }); }); ``` **Python 3.8+** ```python from verify_token import verify_lwc_token user = verify_lwc_token(request.headers.get("Authorization")) if not user: abort(401) # user["id"], user["email"] ``` ## Security model - **Origin allowlist:** only origins listed in `ALLOWED_ORIGINS` inside `index.html` may receive a session. Add your product domains there. The token is posted **only** to the exact requesting origin, never `*`. - **CSRF/replay:** every sign-in uses a random `state` nonce; the SDK ignores any message whose state doesn't match. - **Trust boundary:** the SDK only accepts messages from `https://leewyattcorp.com`. - **Tokens are real LWC user tokens** (full user-level access scoped by RLS). That is appropriate for Brandon's own apps. For untrusted third parties, use the future OAuth2 mode (scoped tokens + consent), not Tier A. - **No secret keys** are shipped - only the public anon key. RLS protects data. ## Adding a new app 1. Add the app's origin (e.g. `https://myapp.com`) to `ALLOWED_ORIGINS` in [`index.html`](index.html) and redeploy `/connect/`. 2. Embed the front-end snippet above with a unique `appId`. 3. Verify the token server-side with the bundled helper. ## Local testing Serve this folder over HTTP and open `example/index.html`: ```bash cd /home/kevin/Desktop/File-Share/Projects/LeeWyatt-Main-Account/connect python3 -m http.server 8910 # open http://localhost:8910/example/ ``` `localhost` / `127.0.0.1` are allowed by `index.html` (`ALLOW_LOCAL_DEV`) so the demo works while testing. This does not weaken production security. ## Files | File | Purpose | | --- | --- | | `index.html` | Hosted authorize/login popup page. | | `wyconnect.js` | Drop-in client SDK (global `WyConnect`). | | `wyconnect.css` | Branded button styles. | | `verify/verify-token.js` | Node server-side token verification. | | `verify/verify_token.py` | Python server-side token verification. | | `example/index.html` | Working demo + integration reference. | ## Future plan: "Continue with Discord" + "Discord Member" label Not built yet - planned. Add a **Continue with Discord** button to the WyConnect login so users can sign in with Discord (we get their email up front), and if they are in the Lee Wyatt Corp / WyCraft Discord server, their profile shows a **Discord Member** label. How it will work (Supabase has a built-in Discord OAuth provider): 1. **Discord app:** create an application at `discord.com/developers`, add OAuth2 redirect `https://cgwxlbenwaiowesagayo.supabase.co/auth/v1/callback`, scopes `identify email guilds`. Client id/secret go in a local secret env file only (never the brain/git). 2. **Enable provider:** Supabase Auth -> Providers -> Discord (client id/secret). Approval-gated remote change. 3. **Button:** on the connect page, `client.auth.signInWithOAuth({ provider: "discord", options: { scopes: "identify email guilds", redirectTo: "https://leewyattcorp.com/connect/" } })`. OAuth uses a redirect (not the email/pw popup path), so the connect page must set `detectSessionInUrl: true` on return and then `postMessage` the session to the opener as usual. Requires adding `https://leewyattcorp.com/connect/` to Supabase `additional_redirect_urls` (Management API PATCH, needs `User-Agent` header for the WAF). 4. **Membership check (the "Discord Member" label):** with the `guilds` scope, the Discord `provider_token` can call `GET https://discord.com/api/users/@me/guilds`; if the Lee Wyatt Corp / WyCraft guild ID is in the list, the user is a member. (Get the guild ID from the WyCraft Discord; invite `discord.gg/dE9UT3DJeQ`, channel `1503197800685502664`.) For roles, use `guilds.members.read` + `GET /users/@me/guilds/{guild.id}/member`. 5. **Store + show:** persist a small flag on `profiles` (e.g. `is_discord_member`, `discord_id`, optional roles) - NOT their full Discord history. Show a "Discord Member" label on the account/profile when true. Re-check on login (membership can change). 6. **Link from settings (existing accounts):** a user who already has a Lee Wyatt Corp account can connect their Discord from account settings (not only at sign-up). Use Supabase `auth.linkIdentity({ provider: "discord" })` while signed in to attach the Discord identity to the current account, then run the same guild-membership check and set the "Discord Member" label. Settings should also allow unlinking (`auth.unlinkIdentity(...)`), which clears the Discord flag. Privacy: request only the scopes needed; store the membership flag + discord id, not message history. Keep all Discord secrets in env files only.