HarperFast / HarperFast/harper
Auth: support multiple named, independently revocable refresh credentials per user
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Problem
Harper stores exactly **one** refresh token per user. `createTokens()` writes a single hash to `hdb_user.refresh_token`:
```ts
// security/tokenAuthentication.ts
const hashedToken = password.hash(refreshToken, password.HASH_FUNCTION.SHA256);
await update(new UpdateObject(SYSTEM_SCHEMA_NAME, SYSTEM_TABLE_NAMES.USER_TABLE_NAME, [
{ username: authObj.username, refresh_token: hashedToken },
]));
```
and `validateRefreshToken` accepts only that one hash (`password.validate(user.refresh_token, token)`).
So minting a refresh token for a user **revokes the previous one**. A user can hold at most one live refresh credential, and the revocation is silent — nothing fails until the displaced holder's next refresh returns 401.
## Why it matters now
`harper login --for-ci` (#1876) hands a refresh token to CI/CD as a durable secret. Under this model:
- Two CI consumers cannot share a user. The second `--for-ci` run silently breaks the first.
- A human who runs an ordinary `harper login` as the CI user breaks the pipeline.
- There is no way to revoke one credential (a compromised runner) without invalidating every other holder — the only lever is rotating the user's single token, which revokes all of them.
- Nothing is auditable: a refresh token carries no identity beyond the username, so you cannot tell which consumer used it or when it was issued.
#1876 mitigates this client-side only — it warns loudly, names the user in the warning, prompts for confirmation on a TTY, and documents that each consumer needs its own Harper user. That is a workaround for a missing server capability, not a fix.
## Proposed
Named, independently revocable credentials — a personal-access-token / API-key model:
- A user may hold **N** named credentials (`name`, `created_at`, `last_used_at`, optional `expires_at`, optional scope/role).
- Storage moves from a single `hdb_user.refresh_token` scalar to a collection keyed by credential id; validation matches any live entry rather than the one.
- Issuing a credential never affects existing ones.
- Each is revocable by name, without touching the others.
- Operations to create / list / revoke, and a `--name` on `harper login --for-ci` so the emitted credential is identifiable at the cluster.
Backwards compatibility: the existing single-token behavior is the degenerate case (one unnamed credential), so current clients keep working while `validateRefreshToken` learns to match a set.
## Scope note
Server-side work; deliberately out of scope for #1876, which is CLI-only. Filed so the client-side warning has something to point at.
Contributor guide
Research direction
Start with security/tokenAuthentication.ts and trace createTokens and validateRefreshToken, then inspect the existing user-table update and authentication paths. Map how refresh credentials are stored and consumed before deciding how named creation, listing, revocation, and legacy single-token behavior fit together. Done means multiple credentials can coexist, be independently revoked, and retain backwards compatibility.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- authentication, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100