Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ curl -X POST http://localhost:8080/v1/login \
Send the returned `access_token` to authenticated endpoints as
`Authorization: Bearer <access_token>`.

The login response also carries a `refresh_token`, valid for 30 days or until
the account expires. Exchange it for a new access token instead of storing the
password:

```sh
curl -X POST http://localhost:8080/v1/token/refresh \
-H 'Content-Type: application/json' \
-d '{"refresh_token":"<refresh_token>"}'
```

Every exchange returns a new `refresh_token` and invalidates the one sent, so
clients must store the new one. Presenting an invalidated token again more than
30 seconds after it was exchanged revokes the whole login session, because it
means the token was copied. `POST /v1/logout` with the same body revokes the
session explicitly. Only a SHA-256 digest of each refresh token is stored.

Every authenticated request reloads the account from the database. Role changes,
regular-user account expiry and account deletion therefore apply to existing
tokens. Admin routes accept only admins; regular-user routes accept only active
Expand Down
9 changes: 9 additions & 0 deletions crates/shaide-common/src/api/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ pub struct AccessTokenResponse {
pub access_token: String,
pub token_type: String,
pub token_expires_in: u64,
/// Exchanged at `/v1/token/refresh` for a new access token. Single use: every exchange
/// returns a new refresh token, and presenting a used one ends the session.
pub refresh_token: String,
pub refresh_token_expires_in: u64,
#[schema(value_type = String, format = DateTime)]
pub account_expires_at: DateTime<Utc>,
pub role: String,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RefreshTokenRequest {
pub refresh_token: String,
}
1 change: 1 addition & 0 deletions crates/shaide-db/migrations/0005_refresh_tokens.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE refresh_tokens;
18 changes: 18 additions & 0 deletions crates/shaide-db/migrations/0005_refresh_tokens.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- Refresh tokens let a client renew its access token without the user's password. Only a
-- SHA-256 digest of each token is stored. Every token rotated out of one login shares a
-- `family_id`, so presenting an already rotated token can revoke the whole session.
CREATE TABLE refresh_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
family_id TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
-- Set when the token is exchanged for its successor.
used_at TIMESTAMP,
-- Set when the token's family is revoked, by logout or detected reuse.
revoked_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now'))
);

CREATE INDEX refresh_tokens_family_id ON refresh_tokens (family_id);
CREATE INDEX refresh_tokens_user_id ON refresh_tokens (user_id);
2 changes: 2 additions & 0 deletions crates/shaide-db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ pub mod error;
pub mod files;
pub mod models;
pub mod organizations;
mod refresh_tokens;
pub mod upload_lifecycle;
mod users;

use std::path::Path;

use anyhow::Result;
pub use models::{InsertModelDAO, ModelDAO};
pub use refresh_tokens::RefreshTokenDAO;
use sqlx::{Pool, Sqlite, SqlitePool, sqlite::SqliteConnectOptions};
pub use users::{UserDAO, UserRole};

Expand Down
Loading
Loading