HarperFast / HarperFast/harper

Auth: support multiple named, independently revocable refresh credentials per user

Open
#2,018 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.