1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! What the four stores share, and how a credential is kept at rest.
//!
//! An authorisation server persists four things: registered clients, live
//! authorisation codes, issued tokens, and the consent a user has already
//! given. Each is a trait in its own module with an in-memory implementation
//! beside it.
//!
//! # Why the database is optional rather than absent
//!
//! An application that is ready to be an OAuth provider already has migrations,
//! a connection pool, and an opinion about what its `oauth_clients` table should
//! look like — whether client ids are UUIDs or slugs, whether a deleted client
//! is a row or a flag, which tenant a client belongs to. A schema forced on it
//! would mean every application either accepted those answers or fought them.
//! So the traits are the contract and a store of your own is four small
//! implementations against tables you already own.
//!
//! **That argument was once used to ship no database stores at all**, and the
//! result was an authorization server nobody could deploy: the in-memory stores
//! are the right answer for tests and a single-process development server, and
//! the wrong answer for anything that restarts or runs more than one worker — a
//! restart silently un-revokes every token, because an empty store answers
//! "unknown" to everything. Not forcing a schema and not providing one are
//! different things.
//!
//! The `database` module therefore ships a store for each trait, behind the `db`
//! feature. Nothing is forced: an application with no database compiles none of
//! it, and one with its own tables implements the traits instead.
//!
//! Whichever you use, **two operations must be atomic**:
//! [`CodeStore::consume`](crate::CodeStore::consume) spends a code exactly once
//! and [`TokenStore::rotate`](crate::TokenStore::rotate) rotates a refresh
//! token exactly once. A read followed by a write satisfies neither and passes
//! every single-threaded test.
use Result;
use ;
use Future;
use Pin;
/// What a store's operations return.
///
/// A boxed future rather than an `async fn` because every store has to be
/// usable as `dyn ClientStore` and friends: the server holds whichever driver
/// the application picked, and cannot be generic over four of them without
/// putting four type parameters on every handler that touches it.
pub type StoreFuture<'a, T> = ;
/// The lowercase-hex SHA-256 of a credential, which is what gets stored.
///
/// Client secrets, authorisation codes, access tokens and refresh tokens are
/// all held this way. Nothing in the database is a usable credential, so a
/// leaked dump is not a leaked set of tokens, and a token can be looked up by
/// hashing what the client presented — no scan, no decryption key to protect.
///
/// # Why SHA-256 and not argon2
///
/// Argon2 is the right answer for a *password*, which is short, chosen by a
/// human, and therefore guessable. None of these values are: every one is 256
/// bits from the OS CSPRNG, so there is nothing to guess and no dictionary to
/// run. What argon2 would add is its deliberate cost — tens of milliseconds and
/// tens of megabytes — on the token endpoint, on the resource-server path, and
/// on every introspection call. That is not defence in depth, it is a
/// denial-of-service an attacker triggers by sending requests.
///
/// The rule this follows: hash passwords slowly, hash random secrets fast.
/// Compare a presented credential against a stored digest.
///
/// Hashing first is not enough on its own: comparing the two digests with `==`
/// returns as soon as they differ, and the timing of that is a measurement of
/// how many leading bytes matched. It is a weaker oracle than comparing the raw
/// secrets, but it is still an oracle, and there is no reason to leave it open.