Two-factor authentication (2FA)
Recommended2FA adds a second factor to every sign-in: after your password, we ask for a 6-digit code from an authenticator app on your phone. Even if your password leaks somewhere else (a reused password on a breached site, a phishing email), an attacker can't sign in without your phone.
Available now
Set it up at Account → Two-factor auth. Takes about two minutes. Free, included in every plan.
Setting it up
-
1
Install an authenticator app
Any RFC 6238 TOTP app works. We've tested with Google Authenticator, 1Password, Authy, Bitwarden and Microsoft Authenticator. If you already use a password manager, prefer that one — recovery is easier later.
-
2
Scan the QR code
Open the authenticator app, add a new account, point your camera at the QR shown at two-factor setup. The setup key is also displayed underneath if your app can't scan QR codes.
-
3
Confirm with a code
The app starts producing a fresh 6-digit code every 30 seconds. Type the current one into the confirm form. This proves the app + your account are linked.
-
4
Save your 10 recovery codes
We show 10 one-time recovery codes ONCE on the success screen. Each works once if you ever lose access to your authenticator. Save them in your password manager — they're shown only on this screen.
Recovery codes
What they're for
If you lose your phone, get a new device, or your authenticator app reset its data, a recovery code lets you sign in once. Use it to enable 2FA on a new device immediately afterward.
Format
Each code is 8 lowercase letters/digits formatted as xxxx-xxxx. You get 10 codes. Each works once and only once — used codes are dead.
Storage
We only store bcrypt hashes of your codes — the plaintext leaves the server once at enrolment and is never seen again. If you lose them, regenerate (the old set stops working) and save the new set.
Regenerate any time
If you suspect any code has been seen by someone else, hit Regenerate recovery codes at your two-factor settings. Old codes — used or unused — stop working immediately.
Disabling 2FA
You can turn 2FA off at your two-factor settings, but the form requires your current password — so a stolen session can't strip the second factor. After disabling, sign-in goes back to password-only.
Lost everything?
Lost your authenticator and your recovery codes? Email support@datascene-tenders.co.uk. We'll re-verify your identity (questions about your account, last bid activity, etc.) and disable 2FA so you can sign back in.
Password storage
Passwords are hashed, not encrypted — meaning even if our database leaked, the plaintext passwords could not be recovered.
- bcrypt cost 12 — ~250ms per verification. Brute-force cost doubles with every cost increment.
- Per-row random salt — two users with the same password get different hashes. Rainbow tables don't apply.
- Constant-time login — wrong-user and wrong-password responses take the same wall time, so the API can't be used to enumerate accounts.
- All sessions invalidated on password change — changing your password kicks every device out, including any compromised ones.
Encryption at rest
Sensitive fields beyond the user record are AES-256-GCM encrypted before they reach the database:
- 2FA secrets — the TOTP shared secret that lives on the server.
- Webhook URLs and signing secrets — Slack, Teams and generic webhook destinations.
- Document library uploads — files in the Central Document Library.
Plaintext is held in memory only at the moment of use (dispatching a webhook, verifying a TOTP code, serving a document download) and is never written to logs.
Sessions & API tokens
- HttpOnly + SameSite=Lax cookies — sessions can't be read by JavaScript and won't ride along on cross-site POSTs.
- Cross-origin request protection on every state-changing request — origin-checked via the browser's Sec-Fetch-Site / Origin headers, on top of a SameSite=Lax session cookie.
- Personal access tokens are scoped — each token carries the minimum capability set (
read:tenders,read:awards, …) and is rate-limited to 60 req/min sustained. - Token hashes stored, not plaintext — the plaintext is shown once at creation. Lose it and you revoke + create a new token.
- Webhook payloads are HMAC-SHA-256 signed — receivers verify the
X-DataScene-Signatureheader to confirm the payload came from us.
Single sign-on (OIDC)
EnterpriseSign in through your existing identity provider — Okta, Azure AD, Google Workspace, JumpCloud, OneLogin, or any OpenID Connect-compatible IdP. Users in your allowed email domains can join your workspace automatically.
Platform Google & Microsoft
When enabled, the login page shows "Sign in with Google" and "Sign in with Microsoft" buttons. Returning users with a matching email are signed in; new users are not auto-provisioned (admins invite them first).
Per-org IdP (BYO)
Admins configure their own IdP at your SSO settings — issuer URL, client ID, encrypted client secret, allowed email domains. Just-in-time provisioning creates accounts on first sign-in.
Token verification
ID tokens are verified against the IdP's JWKS — signature, issuer, audience and expiry are all checked on every callback. A leaked OAuth code can't be replayed.
Client secret encrypted
The IdP client secret is encrypted with the master key at rest. The admin form never echoes it back — to rotate, paste the new value from your IdP.
Activity log
Every sign-in, password change, bid edit and team-management action your organisation has taken — in one chronological feed at the audit log. Use it to spot anything you don't recognise and to satisfy compliance review.
What's tracked
- Sign-ins (success, failure, 2FA, SSO), sign-outs.
- Password changes, email changes, 2FA enable/disable, recovery-code rotation.
- API tokens created & revoked, with scope metadata.
- Team invitations, invitation revokes, admin role transfers.
- Bid changes (create, update, status, sign-off, delete) and clarification Q&A.
- Saved-search create/delete, notification channel add/remove, report create/delete.
- SSO configuration changes, activity-log CSV download / email exports.
- Each entry: actor email, IP, user-agent, timestamp, action-specific metadata.
Filter, download & email
Filter by action and date range. Two one-click exports:
- Download CSV — up to 5,000 rows matching the current filter set. Drops straight into a spreadsheet for compliance review.
- Email this view — sends the filtered table to every email channel configured on your account. Top 100 entries inline, with a deep-link back to the full report.
Both exports themselves write an audit-log entry — so a user exfiltrating the log shows up in the next review.
Workspace roles & admin transfer
Each workspace has exactly one admin at any moment. The first person to create the workspace is the admin; everyone else joins as a member. Admin is transferred deliberately — never by side-effect.
What the admin can do
- • Invite + revoke team members
- • Configure SSO, channel webhooks, branding
- • Transfer the admin role to another member
- • Manage the workspace logo
What members can do
- • Everything else: search tenders, manage bids, comment, ask clarifications, upload to bid library, schedule reports
- • Cannot invite users or change workspace-wide settings
Transferring admin
-
1
Open your team settings. Each non-admin member's row shows a Make admin button (admin-side only).
-
2
Click it. Confirm in the dialog ("Transfer the admin role to {Name}? You will become a regular member.").
-
3
The swap happens in a single database transaction — there's never a moment with zero or two admins. The audit log records the transfer with both user IDs.
Pick your successor before you leave
There's no self-demote-without-successor flow. If you're leaving the company, transfer admin before you go — otherwise the workspace has no admin, which means no one can invite users, manage SSO, or rotate workspace-wide settings.
What we won't do
- Send your password by email, ever.
- Ask for your 2FA code outside the actual login or settings pages.
- Send 2FA codes by SMS (SIM swap attacks make SMS-based 2FA unsafe).
- Show your password or recovery codes after the moment of creation.
- Read your bid library or document content for any cross-org feature.
Operating the master encryption key
If you're running DataScene Tenders on your own infrastructure, the NOTIFICATION_SECRET_KEY environment variable is the master AES-256-GCM key that encrypts your users' 2FA secrets, webhook URLs and webhook signing secrets at rest.
Treat this key the way you'd treat your database backup encryption key
Losing it locks every 2FA user out and breaks every Slack/Teams/webhook integration. Leaking it gives an attacker with DB access the decrypted secrets.
✓ Where to store it
- • Production secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, 1Password, Doppler)
- • Injected as env var at process start
- • Backed up offline — a printed paper copy in a sealed envelope is a legit fallback
✗ Where not to store it
- • Git (even private repos)
- •
.envfiles committed alongside code - • CI build logs, Slack messages, "dev keys" docs
Format: base64-encoded 32 raw bytes. Generate with openssl rand -base64 32. The backend refuses to start if the value is set but isn't exactly 32 bytes after decode, and refuses to enrol 2FA / accept webhook channels if the value is unset.
Report a security issue
Found something concerning? Email security@datascene-tenders.co.uk. We acknowledge within 24 hours, 7 days a week.