oauth-as 0.9.0

An embeddable OAuth 2.1 Authorization Server library: spec-mirroring types (RFC 6749, RFC 8628, RFC 7636), a full device-authorization-grant state machine, and a storage trait the host implements. Deliberately host-agnostic with a tiny dependency set; nothing is allocated until the host constructs an AuthorizationServer, so an embedding host pays zero memory until its config enables the feature.
Documentation
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (C) 2026 Matthew Jackson

//! RFC 7591 dynamic client registration and RFC 7592 registration management.
//!
//! # Read this before you turn it on (RFC 7591 section 5)
//!
//! Dynamic registration lets a caller CREATE A CLIENT. Section 5 of RFC 7591 says so plainly: an
//! open registration endpoint is available to anyone on the internet, and the authorization server
//! "MUST" treat what it is handed as untrusted, because a registration is an attacker-chosen
//! `client_name` on a consent screen, an attacker-chosen redirect URI, and an identity that every
//! later threat model quietly assumes is scarce. This crate's own security review made the same
//! point from the other side: several residual risks here are bounded by "the attacker must
//! control a registered client", and dynamic registration turns that from an assumption into a
//! form submission.
//!
//! So the shape of this module is decided by that, not by convenience:
//!
//! - It is OFF unless [`crate::server::ServerConfig::registration`] is `Some`. There is no
//!   "enabled by default", no environment variable, and no way to reach it by accident. A host
//!   that does not set that field cannot be dynamically registered against, and pays 8 bytes and
//!   no allocation for the privilege.
//! - Enabling it is not enough. A [`RegistrationPolicy`] must also be installed (see
//!   [`crate::server::AuthorizationServer::with_registration_policy`]), and with none installed
//!   every registration is REFUSED. That is the opposite default to the rate limiter, and the
//!   reasoning is in [`crate::events::Hooks::registration_policy`].
//! - What a registrant may ask for is bounded by [`RegistrationConfig`]: which grants, which
//!   scopes. The defaults are the narrow ones (no `client_credentials`, no device grant, no
//!   scopes), because the widening should be a sentence the host wrote.
//! - Every registration, update and deletion is an audit event
//!   ([`crate::events::Event::ClientRegistered`] and friends), carrying no credential.
//!
//! # The registration access token (RFC 7592 section 2)
//!
//! Management is authenticated by a bearer token minted at registration. It reads, REWRITES and
//! DELETES a registration, so it is at least as powerful as the client secret it sits next to, and
//! it is stored exactly the same way: as a one-way [`crate::client::SecretHash`], compared in
//! constant time, never in plaintext and never with `==`.
//!
//! That has one visible consequence, and it is a deliberate deviation worth stating rather than
//! burying. RFC 7592 section 3 lists `registration_access_token` (and, for a confidential client,
//! `client_secret`) as members of the client information response, which the read and update
//! responses of sections 2.1 and 2.2 also use. This server cannot return either on a read or an
//! update, because it does not have them: it kept a verifier, not the credential. Both are
//! returned exactly once, by the registration that minted them, and after that the client holds
//! the only copy. The alternative is storing two live bearer credentials in plaintext for the
//! lifetime of every registration, which is the thing [`crate::client::SecretHash`] exists to stop.
//!
//! # What is NOT implemented, and why
//!
//! - `software_statement` and `software_id` (RFC 7591 sections 2.3 and 3.1.1). A software
//!   statement is a JWT that has to be verified against a trust anchor the HOST owns, and there is
//!   no honest default for "which issuers do you trust to vouch for a client". A request carrying
//!   one is REFUSED with `invalid_software_statement` rather than ignored: RFC 7591 section 2
//!   tells a server to ignore metadata it does not understand, but a software statement is an
//!   assertion the client believes is being HONOURED, and silently dropping it would register a
//!   client on terms nobody agreed to.
//! - The optional human-readable metadata of RFC 7591 section 2 (`client_uri`, `logo_uri`,
//!   `contacts`, `tos_uri`, `policy_uri`). They are ignored, as section 2 permits, because this
//!   server renders no branded consent screen, so storing them would be storing
//!   attacker-supplied strings for no purpose.
//! - `jwks` and `jwks_uri` (RFC 7591 section 2), and this one is a GAP rather than a decision.
//!   The crate does RFC 7523 client assertions under the `client_assertion` feature, so a key
//!   registered here would be a key the token endpoint could use; modelling these two members is
//!   most of what it would take to make `private_key_jwt` registrable, and it is not done. Until
//!   it is, a `private_key_jwt` client is one the HOST provisions out of band, and asking to
//!   register one is refused rather than accepted-and-ignored (see the note on the
//!   `AUTH_METHOD_*` constants below).

use serde::{Deserialize, Serialize};

use crate::client::{Client, ClientAuth, ClientId, DynamicRegistration, SecretHash};
use crate::grant::GrantType;
use crate::scope::ScopeSet;
use crate::server::{AuthorizationServer, Clock, ServerConfig};
use crate::store::{Storage, StorageError};

/// RFC 7591 section 2 client metadata: the registration request body, and the echoed half of the
/// section 3.2.1 client information response.
///
/// Only the members this server actually acts on are modelled. RFC 7591 section 2 requires a
/// server to IGNORE metadata it does not understand, which is what `serde`'s default handling of
/// unknown fields does here, so a client that sends `logo_uri` is not refused for it.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientMetadata {
    /// Redirection URIs (RFC 6749 section 3.1.2). REQUIRED for a client registering the
    /// authorization code grant, since that grant has nowhere to deliver a code without one.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub redirect_uris: Vec<String>,
    /// How the client will authenticate at the token endpoint. Omitted means
    /// `client_secret_basic`, which is the default RFC 7591 section 2 states.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub token_endpoint_auth_method: Option<String>,
    /// The grants this client will use. OMITTED (`None`) means `["authorization_code"]`
    /// (section 2), which is why this is an `Option` rather than a `Vec` that is empty when
    /// absent: section 2 gives omission a meaning, and an explicitly empty list means the opposite
    /// of that meaning. Collapsing the two would make `{"grant_types": []}` silently register the
    /// authorization code grant.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub grant_types: Option<Vec<String>>,
    /// The authorization response types this client will use. Omitted (`None`) means `["code"]`
    /// (section 2); see [`ClientMetadata::grant_types`] for why this is an `Option`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub response_types: Option<Vec<String>>,
    /// Human-readable name, shown to a resource owner. ATTACKER-CHOSEN when registration is open:
    /// it is echoed into this crate's device verification page, which escapes it, and any host
    /// consent screen must do the same.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub client_name: Option<String>,
    /// The space-delimited scope list (RFC 6749 section 3.3) this client may request.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub scope: Option<String>,
    /// RFC 7591 section 2.3. NOT evaluated by this server, and its presence is a refusal rather
    /// than an omission: see the module docs.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub software_statement: Option<String>,
}

/// The RFC 7591 section 3.2.1 client information response, which RFC 7592 section 3 reuses for
/// read and update.
///
/// `Debug` is hand-written (below) because two of these fields are live bearer credentials at the
/// moment this value exists, and this is the value a host is most likely to log: it is what it is
/// about to serialize.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientInformation {
    /// REQUIRED (section 3.2.1). The identifier this server minted.
    pub client_id: String,
    /// Present only for a confidential registration, and only on the response that MINTED it: see
    /// the module docs on why a read cannot return it.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub client_secret: Option<String>,
    /// Seconds since the Unix epoch (section 3.2.1).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub client_id_issued_at: Option<u64>,
    /// REQUIRED when a `client_secret` is issued (section 3.2.1). `0` means it never expires.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub client_secret_expires_at: Option<u64>,
    /// RFC 7592 section 3. Present only on the response that minted it.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub registration_access_token: Option<String>,
    /// RFC 7592 section 3: where this registration is read, updated and deleted. Absent when the
    /// host did not enable management.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub registration_client_uri: Option<String>,
    /// The registered metadata, echoed as section 3.2.1 requires, INCLUDING any value this server
    /// substituted for what was asked (section 3.2.1: the response reflects what was registered,
    /// not what was requested).
    #[serde(flatten)]
    pub metadata: ClientMetadata,
}

/// Hand-written so neither credential reaches a debug format, on the same reasoning as
/// [`crate::client::ClientAuth`]'s and [`crate::server::TokenRequest`]'s. The Some/None
/// distinction is kept: "a secret was issued" is registration shape, not a secret.
impl std::fmt::Debug for ClientInformation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
            value.as_ref().map(|_| "[redacted]")
        }
        f.debug_struct("ClientInformation")
            .field("client_id", &self.client_id)
            .field("client_secret", &redact_opt(&self.client_secret))
            .field("client_id_issued_at", &self.client_id_issued_at)
            .field("client_secret_expires_at", &self.client_secret_expires_at)
            .field(
                "registration_access_token",
                &redact_opt(&self.registration_access_token),
            )
            .field("registration_client_uri", &self.registration_client_uri)
            .field("metadata", &self.metadata)
            .finish()
    }
}

/// The RFC 7591 section 3.2.2 error codes.
///
/// A SEPARATE registry from RFC 6749 section 5.2, and modelled separately for that reason: the two
/// share no value, they are returned by different endpoints, and collapsing them into
/// [`crate::error::ErrorCode`] would let a token-endpoint code be emitted here (or the reverse)
/// with nothing to catch it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RegistrationErrorCode {
    /// One or more `redirect_uris` is invalid: not an absolute URI, or carrying a fragment, or
    /// absent for a grant that needs one.
    InvalidRedirectUri,
    /// Some other submitted metadata value is invalid, or names something this server will not
    /// register.
    InvalidClientMetadata,
    /// The software statement presented is invalid. This server evaluates none, so any is: see
    /// the module docs.
    InvalidSoftwareStatement,
}

impl RegistrationErrorCode {
    /// The registered wire spelling.
    pub fn as_str(self) -> &'static str {
        match self {
            RegistrationErrorCode::InvalidRedirectUri => "invalid_redirect_uri",
            RegistrationErrorCode::InvalidClientMetadata => "invalid_client_metadata",
            RegistrationErrorCode::InvalidSoftwareStatement => "invalid_software_statement",
        }
    }
}

impl std::fmt::Display for RegistrationErrorCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

/// The RFC 7591 section 3.2.2 error response body: a 400 with `error` and an optional
/// `error_description`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegistrationErrorResponse {
    /// The registered code.
    pub error: RegistrationErrorCode,
    /// Human-readable detail for the developer.
    ///
    /// `Cow<'static, str>` for the same reason [`crate::error::ErrorResponse`] uses one: every
    /// refusal `validate` can produce describes a RULE rather than a value, so the description is
    /// always a string constant, and RFC 7591 section 1.2 makes the initial access token optional,
    /// which means a host may expose this endpoint to unauthenticated callers who then choose its
    /// refusal rate. Size neutral: `Option<Cow<'static, str>>` is 24 bytes, exactly what
    /// `Option<String>` was.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_description: Option<std::borrow::Cow<'static, str>>,
}

impl RegistrationErrorResponse {
    /// An error with a description attached.
    pub fn new(
        error: RegistrationErrorCode,
        description: impl Into<std::borrow::Cow<'static, str>>,
    ) -> Self {
        RegistrationErrorResponse {
            error,
            error_description: Some(description.into()),
        }
    }
}

impl std::fmt::Display for RegistrationErrorResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.error_description {
            Some(d) => write!(f, "{}: {d}", self.error),
            None => f.write_str(self.error.as_str()),
        }
    }
}

/// For the reason [`crate::error::ErrorResponse`] is one: this is the value a host is handed when
/// a registration is refused, and a host that propagates it with `?` into a `Box<dyn Error>`
/// should not have to care which of the two sibling refusal bodies it is holding. There is no
/// `source`: the refusal describes a rule this server applied, not a failure underneath it.
impl std::error::Error for RegistrationErrorResponse {}

/// Why a registration or management request was refused.
///
/// One enum for RFC 7591 and RFC 7592 because the two endpoints share every failure mode they have
/// in common, and splitting them would make a host match twice on the same four cases.
#[derive(Debug)]
#[non_exhaustive]
pub enum RegistrationFailure {
    /// This server does not offer the endpoint: the host set no
    /// [`crate::server::ServerConfig::registration`], or set one with management turned off. A 404
    /// on the wire, because the honest answer to "is there a registration endpoint here" is no.
    Disabled,
    /// The RFC 7591 section 1.2 initial access token, or the RFC 7592 section 2 registration
    /// access token, was missing, wrong, or refused by the host's [`RegistrationPolicy`]. 401,
    /// with an RFC 6750 section 3 `Bearer` challenge.
    ///
    /// Deliberately ONE answer for all of those, including "no such registration": distinguishing
    /// them would turn this endpoint into an oracle for which client ids exist, exactly as
    /// `invalid_client` collapses the same two cases at the token endpoint.
    Unauthorized,
    /// The metadata is not acceptable (RFC 7591 section 3.2.2). 400.
    Invalid(RegistrationErrorResponse),
    /// The storage seam failed. 500, and the wire learns nothing else.
    Storage(StorageError),
}

impl RegistrationFailure {
    /// The HTTP status this refusal takes.
    pub fn http_status(&self) -> u16 {
        match self {
            RegistrationFailure::Disabled => 404,
            RegistrationFailure::Unauthorized => 401,
            RegistrationFailure::Invalid(_) => 400,
            RegistrationFailure::Storage(_) => 500,
        }
    }
}

impl std::fmt::Display for RegistrationFailure {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RegistrationFailure::Disabled => f.write_str("dynamic client registration is disabled"),
            RegistrationFailure::Unauthorized => f.write_str("not authorized to register"),
            RegistrationFailure::Invalid(e) => write!(f, "{e}"),
            RegistrationFailure::Storage(e) => write!(f, "{e}"),
        }
    }
}

impl std::error::Error for RegistrationFailure {}

/// What the host's [`RegistrationPolicy`] is told about one attempt.
///
/// Everything borrows: the policy is called inside the request the host is already driving.
/// `#[non_exhaustive]` because later releases will have more to say about the caller.
#[non_exhaustive]
pub struct RegistrationAttempt<'a> {
    /// The RFC 7591 section 1.2 initial access token the request presented, if any. This crate
    /// does not interpret it: the host decides what an acceptable one is, because it is the host
    /// that issued it (or that recognises an allowlisted API key, or a signed invite, or nothing
    /// at all).
    pub initial_access_token: Option<&'a str>,
    /// The metadata being registered, parsed but NOT yet validated, so a policy can refuse on
    /// content (a `client_name` impersonating the deployment, a redirect URI on a domain the host
    /// will not serve) before this server ever writes it down.
    pub metadata: &'a ClientMetadata,
}

/// Hand-written: the initial access token is a bearer credential, and this struct exists only
/// inside a request path, which is precisely where a host is most likely to debug-print it.
impl std::fmt::Debug for RegistrationAttempt<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RegistrationAttempt")
            .field(
                "initial_access_token",
                &self.initial_access_token.map(|_| "[redacted]"),
            )
            .field("metadata", self.metadata)
            .finish()
    }
}

/// What a [`RegistrationPolicy`] decided.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegistrationDecision {
    /// Register the client, subject to the metadata being valid.
    Allow,
    /// Refuse. The wire gets 401 and nothing else; the host knows why and this crate does not
    /// need to.
    Deny,
}

/// Who may create a client here. THIS LIBRARY HAS NO OPINION, and cannot have one: it never sees
/// a request, so it has no caller, no IP, no tenant and no invite list.
///
/// With NO policy installed, every registration is denied. That is not a safe default chosen for
/// tidiness, it is the only reading of RFC 7591 section 5 that does not leave an anonymous
/// client-minting endpoint on the internet because somebody set a config field and moved on. A
/// host that genuinely wants an open endpoint writes a two-line policy that returns
/// [`RegistrationDecision::Allow`], and that line is then something a reviewer can find.
pub trait RegistrationPolicy: Send + Sync {
    /// Decide whether this attempt may create a client. Called BEFORE the metadata is validated
    /// and before anything is written, so a refusal costs one call and touches no storage.
    fn authorize(&self, attempt: &RegistrationAttempt<'_>) -> RegistrationDecision;
}

/// What dynamic registration is allowed to produce here. Held behind
/// [`crate::server::ServerConfig::registration`], which is `None` (registration off) by default.
///
/// Every bound below is a CEILING on what an anonymous, or merely policy-approved, registrant can
/// obtain. The defaults are the narrow ones on purpose; see [`RegistrationConfig::new`].
#[derive(Debug, Clone, PartialEq, Eq)]
/// `#[non_exhaustive]`: this struct's field set VARIES WITH CARGO FEATURES, so a host that writes a
/// full struct literal has a build that breaks the day anything in their dependency graph enables a
/// feature they did not ask for. Construct with `new()` and assign the fields you want. This is the
/// one attribute on this type that cannot be added after publication, because by then somebody's
/// struct literal is in production.
#[non_exhaustive]
pub struct RegistrationConfig {
    /// RFC 8414 section 2 `registration_endpoint`. `None` derives `{issuer}/register`.
    pub registration_endpoint: Option<String>,
    /// The grants a registration may ask for. Anything outside this list is
    /// `invalid_client_metadata`.
    pub allowed_grant_types: Vec<GrantType>,
    /// The ceiling on a registration's `scope`. A request outside it is `invalid_client_metadata`;
    /// the default is EMPTY, so a host must say what a registrant may reach.
    pub allowed_scopes: ScopeSet,
    /// How long an issued client secret lives. `None` (the default) means it never expires, which
    /// is what `client_secret_expires_at: 0` says on the wire (RFC 7591 section 3.2.1).
    pub client_secret_ttl: Option<std::time::Duration>,
    /// Whether RFC 7592 read, update and delete are offered at all. `true` by default: a client
    /// that can be created and never corrected or deleted leaves the host doing registration
    /// lifecycle by hand.
    pub management_enabled: bool,
}

impl Default for RegistrationConfig {
    fn default() -> Self {
        RegistrationConfig::new()
    }
}

impl RegistrationConfig {
    /// The narrow defaults: the authorization code grant with refresh, no scopes, management on.
    ///
    /// `client_credentials` and the device grant are deliberately absent. A `client_credentials`
    /// registration mints tokens with NO resource owner anywhere in the picture, so an open
    /// registration endpoint that grants it is an open token endpoint; the device grant makes the
    /// registrant able to allocate user codes, which RFC 8628 section 5.1 says are only adequate
    /// in combination with rate limiting. Both are one line for a host to add, with its eyes open.
    pub fn new() -> Self {
        RegistrationConfig {
            registration_endpoint: None,
            allowed_grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
            allowed_scopes: ScopeSet::default(),
            client_secret_ttl: None,
            management_enabled: true,
        }
    }

    /// The advertised registration endpoint for `issuer`.
    pub fn endpoint(&self, issuer: &str) -> String {
        match &self.registration_endpoint {
            Some(url) => url.clone(),
            None => format!("{}/register", issuer.trim_end_matches('/')),
        }
    }
}

/// The largest number of `redirect_uris` one dynamic registration may declare.
///
/// # Why there is a cap at all
///
/// RFC 7591 section 2 sets none, and the list is not read here: it is read on EVERY authorization
/// request for the registered client, as a linear scan with exact string comparison, because
/// OAuth 2.1 section 4.1.3 forbids anything cheaper than exact matching. So an unbounded list is a
/// cost bought once, at an endpoint whose [`RegistrationPolicy`] a host may well have opened to
/// anonymous callers, and then paid per request for as long as the registration exists. That
/// durability is what makes this worth a constant rather than a shrug.
///
/// # Why 16
///
/// It is counted from what a real client needs: one redirect URI per deployment environment
/// (production, staging, a review app or two) times one per platform that needs its own
/// (web, a native custom scheme, a loopback range for a desktop app). That is a handful, and
/// sixteen is several times a handful. A registrant that genuinely needs more has one client
/// standing in for several, which is a modelling problem this cap makes visible rather than a
/// limit it imposes; separate clients also give the deployment separate secrets and separate
/// revocation, which is the better shape anyway.
///
/// # Why the scan stays linear
///
/// At sixteen it is faster than hashing, and it is the same argument as
/// [`crate::server::MAX_RESOURCE_INDICATORS`]. The defect was the missing bound, not the loop.
pub const MAX_REGISTERED_REDIRECT_URIS: usize = 16;

// The `token_endpoint_auth_method` values one may REGISTER here, which are a strict SUBSET of the
// ones RFC 8414 `token_endpoint_auth_methods_supported` advertises, and deliberately so. That
// document describes the TOKEN ENDPOINT (RFC 8414 s2), which really does accept
// `client_secret_jwt`, `private_key_jwt`, `tls_client_auth` and `self_signed_tls_client_auth` for
// a client the host provisioned out of band; narrowing it to this list would lie to every
// statically configured client that uses one.
//
// The other four are out of reach of REGISTRATION, not of the server:
//
// - `private_key_jwt` and `client_secret_jwt` need a key. [`ClientMetadata`] models neither `jwks`
//   nor `jwks_uri` (RFC 7591 s2), so there is nowhere for a registrant to put one, and
//   `client_secret_jwt` additionally needs the shared secret IN THE CLEAR at verification time
//   while a registration keeps only a one-way [`SecretHash`].
// - `tls_client_auth` and `self_signed_tls_client_auth` need the RFC 8705 s2.1.1 subject
//   parameters (`tls_client_auth_subject_dn` and the four SAN forms), which are likewise not
//   modelled.
//
// Accepting any of the four would mint a registration the token endpoint could never honour,
// which is worse than refusing it: RFC 7591 s3.2.2 gives `invalid_client_metadata` for exactly
// this, a value the server will not register. Closing the gap means modelling `jwks`/`jwks_uri`
// and the RFC 8705 subject parameters, and that is the change to make, not a wider list here.
const AUTH_METHOD_NONE: &str = "none";
const AUTH_METHOD_BASIC: &str = "client_secret_basic";
const AUTH_METHOD_POST: &str = "client_secret_post";

/// RFC 7591 section 2: the `response_type` that corresponds to the authorization code grant. It is
/// also the only one OAuth 2.1 keeps, the implicit grant's `token` having been removed.
const RESPONSE_TYPE_CODE: &str = "code";

/// Whether a redirect URI is one this server can ever match.
///
/// The strictness here is set by what the AUTHORIZATION endpoint does with it, not by what looks
/// tidy. `AuthorizationServer::validate_authorization_request` compares the requested
/// `redirect_uri` against the registered one by EXACT STRING MATCH (OAuth 2.1 section 4.1.3), so a
/// registration this server accepts but can never match is not a lenient registration, it is a
/// client that will be told `invalid_request` forever with no way to find out why. RFC 6749
/// section 3.1.2 settles both halves: the URI MUST be absolute, and it MUST NOT carry a fragment.
///
/// Delegated to the RFC 8707 resource-indicator check rather than restated, because that function
/// already implements exactly this rule (absolute URI, no fragment, nothing outside printable
/// ASCII, which RFC 3986 requires of a URI anyway) and two copies of one rule drift.
fn redirect_uri_is_registerable(value: &str) -> bool {
    crate::authorization::is_valid_resource_indicator(value)
}

/// The metadata as this server will actually record it.
#[derive(Debug)]
struct Registered {
    redirect_uris: Vec<String>,
    grant_types: Vec<GrantType>,
    response_types: Vec<String>,
    token_endpoint_auth_method: String,
    scope: ScopeSet,
    client_name: Option<String>,
}

/// Validate one RFC 7591 section 2 metadata document against what this deployment will register.
///
/// Every refusal here is a registration this server would otherwise have written down and then
/// been unable to honour. That is the standard the rules are set to: not "is this plausible" but
/// "will the endpoints that later read this record be able to act on it".
fn validate(
    metadata: &ClientMetadata,
    config: &RegistrationConfig,
) -> Result<Registered, RegistrationFailure> {
    // RFC 7591 s2.3 / s3.2.2. First, because a client that sent one is asking to be registered on
    // terms this server has not read, and nothing after this point would be the registration it
    // asked for. See the module docs for why this is a refusal and not an ignored member.
    if metadata.software_statement.is_some() {
        return Err(RegistrationFailure::Invalid(
            RegistrationErrorResponse::new(
                RegistrationErrorCode::InvalidSoftwareStatement,
                "this server does not evaluate software statements (RFC 7591 s2.3)",
            ),
        ));
    }

    // RFC 7591 s2: absent `grant_types` defaults to `["authorization_code"]`.
    let grant_types: Vec<GrantType> =
        match metadata.grant_types.as_deref() {
            None => vec![GrantType::AuthorizationCode],
            Some(values) => {
                let mut out = Vec::with_capacity(values.len());
                for value in values {
                    // An unknown grant type is refused rather than dropped: see the test in
                    // `src/tests/registration.rs`. `implicit` and `password` land here too, which is
                    // right, because OAuth 2.1 removes both.
                    // `GrantType::parse` and not `value.parse()`: the refusal below discards the
                    // value, and `FromStr`'s error would first copy the caller's string onto the
                    // heap to carry it there. The registration document is caller-supplied text of
                    // the caller's chosen length, same as `grant_type` at the token endpoint.
                    let grant: GrantType = GrantType::parse(value).ok_or_else(|| {
                        RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                            RegistrationErrorCode::InvalidClientMetadata,
                            "grant_types names a grant this server does not implement",
                        ))
                    })?;
                    if !config.allowed_grant_types.contains(&grant) {
                        return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                        RegistrationErrorCode::InvalidClientMetadata,
                        "grant_types names a grant this deployment does not offer registrants",
                    )));
                    }
                    if !out.contains(&grant) {
                        out.push(grant);
                    }
                }
                out
            }
        };
    let uses_code = grant_types.contains(&GrantType::AuthorizationCode);

    // RFC 7591 s2 spells out the correspondence between the two lists (`authorization_code` with
    // `code`, `implicit` with `token`). It permits a server to reject OR to substitute; rejecting
    // is the choice here, because substituting registers a client that asked for something else
    // and tells it so only in the echoed response it may not re-read.
    //
    // OAuth 2.1 has exactly one response type left, so the whole correspondence reduces to: the
    // list is `["code"]` if and only if the authorization code grant is registered.
    let response_types: Vec<String> =
        match metadata.response_types.as_deref() {
            // s2: absent defaults to `["code"]`, which is only coherent when the code grant is there.
            None => match uses_code {
                true => vec![RESPONSE_TYPE_CODE.to_string()],
                false => Vec::new(),
            },
            // An EXPLICIT empty list falls through here and is caught by the correspondence check
            // below when the code grant is registered: the client said it uses no response type while
            // asking for the one grant that has one.
            Some(values) => {
                for value in values {
                    if value != RESPONSE_TYPE_CODE {
                        return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                        RegistrationErrorCode::InvalidClientMetadata,
                        "this server issues authorization codes only; OAuth 2.1 removes the \
                         implicit grant",
                    )));
                    }
                }
                let asks_for_code = !values.is_empty();
                // The correspondence, in both directions: `code` without `authorization_code` is a
                // response type nothing will produce, and `authorization_code` without `code` is a
                // grant with no way to start.
                if asks_for_code != uses_code {
                    return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                    RegistrationErrorCode::InvalidClientMetadata,
                    "grant_types and response_types must correspond: authorization_code with \
                     code (RFC 7591 s2)",
                )));
                }
                match asks_for_code {
                    true => vec![RESPONSE_TYPE_CODE.to_string()],
                    false => Vec::new(),
                }
            }
        };

    // RFC 7591 s2 makes `redirect_uris` required for a redirection-based flow, and s3.2.2 gives
    // the missing case and the malformed case the same code, because both say the same thing: this
    // client has no address a code can be delivered to.
    if uses_code && metadata.redirect_uris.is_empty() {
        return Err(RegistrationFailure::Invalid(
            RegistrationErrorResponse::new(
                RegistrationErrorCode::InvalidRedirectUri,
                "the authorization_code grant requires at least one redirect_uri",
            ),
        ));
    }
    // A CAP, because a registration is durable and the cost it imposes is not paid here. Every
    // authorization request for this client scans `redirect_uris` linearly with exact string
    // comparison (OAuth 2.1 section 4.1.3 allows nothing cheaper), so an unbounded list bought
    // once at an endpoint a policy may well have opened to anonymous callers is a per-request cost
    // that lasts as long as the registration does. RFC 7591 section 2 sets no bound of its own.
    if metadata.redirect_uris.len() > MAX_REGISTERED_REDIRECT_URIS {
        return Err(RegistrationFailure::Invalid(
            RegistrationErrorResponse::new(
                RegistrationErrorCode::InvalidRedirectUri,
                "too many redirect_uris",
            ),
        ));
    }
    for uri in &metadata.redirect_uris {
        if !redirect_uri_is_registerable(uri) {
            // The offending value is NOT echoed: it is attacker-supplied and this description
            // goes into an error body and quite possibly a log line.
            return Err(RegistrationFailure::Invalid(
                RegistrationErrorResponse::new(
                    RegistrationErrorCode::InvalidRedirectUri,
                    "each redirect_uri must be an absolute URI with no fragment (RFC 6749 s3.1.2)",
                ),
            ));
        }
    }

    // RFC 7591 s2: absent `token_endpoint_auth_method` defaults to `client_secret_basic`.
    let token_endpoint_auth_method = metadata
        .token_endpoint_auth_method
        .clone()
        .unwrap_or_else(|| AUTH_METHOD_BASIC.to_string());
    if !matches!(
        token_endpoint_auth_method.as_str(),
        AUTH_METHOD_NONE | AUTH_METHOD_BASIC | AUTH_METHOD_POST
    ) {
        return Err(RegistrationFailure::Invalid(
            RegistrationErrorResponse::new(
                RegistrationErrorCode::InvalidClientMetadata,
                "token_endpoint_auth_method is not one this server can REGISTER; RFC 8414 \
                 token_endpoint_auth_methods_supported describes the token endpoint, which \
                 accepts more",
            ),
        ));
    }
    // RFC 6749 s4.4 gives client credentials to confidential clients only, so this pair produces a
    // registration whose only grant the token endpoint will refuse every time. Same argument as
    // the redirect URI rule above.
    if token_endpoint_auth_method == AUTH_METHOD_NONE
        && grant_types.contains(&GrantType::ClientCredentials)
    {
        return Err(RegistrationFailure::Invalid(
            RegistrationErrorResponse::new(
                RegistrationErrorCode::InvalidClientMetadata,
                "client_credentials requires a confidential client (RFC 6749 s4.4)",
            ),
        ));
    }

    // RFC 6749 s3.3 syntax, then the deployment's ceiling. Both are `invalid_client_metadata`:
    // s3.2.2 has one code for a metadata value this server will not accept.
    let scope =
        match metadata.scope.as_deref() {
            None => ScopeSet::empty(),
            Some(s) => {
                let requested = ScopeSet::parse(s).map_err(|_| {
                    RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                        RegistrationErrorCode::InvalidClientMetadata,
                        "scope is not a space-delimited RFC 6749 s3.3 token list",
                    ))
                })?;
                if !requested.is_subset(&config.allowed_scopes) {
                    return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
                    RegistrationErrorCode::InvalidClientMetadata,
                    "scope exceeds what this deployment offers dynamically registered clients",
                )));
                }
                requested
            }
        };

    Ok(Registered {
        redirect_uris: metadata.redirect_uris.clone(),
        grant_types,
        response_types,
        token_endpoint_auth_method,
        scope,
        client_name: metadata.client_name.clone(),
    })
}

/// Rebuild the RFC 7591 section 2 view of a stored registration, for the section 3.2.1 echo.
fn registered_metadata(client: &Client, registration: &DynamicRegistration) -> ClientMetadata {
    ClientMetadata {
        redirect_uris: client.redirect_uris.clone(),
        token_endpoint_auth_method: Some(registration.token_endpoint_auth_method.clone()),
        grant_types: Some(client.grant_types.iter().map(|g| g.to_string()).collect()),
        // Derived rather than stored: `validate` enforces the RFC 7591 section 2 correspondence
        // between the two lists, so the response types are a function of the grant types and
        // storing them separately would only create a second place for them to disagree.
        response_types: Some(match client.allows_grant(GrantType::AuthorizationCode) {
            true => vec![RESPONSE_TYPE_CODE.to_string()],
            false => Vec::new(),
        }),
        client_name: client.name.clone(),
        scope: (!client.allowed_scopes.is_empty()).then(|| client.allowed_scopes.to_string()),
        // Never echoed: none was accepted, so echoing one would say it had been.
        software_statement: None,
    }
}

impl<S: Storage, C: Clock> AuthorizationServer<S, C> {
    /// The registration configuration, when the host enabled it.
    fn registration_config(&self) -> Result<&RegistrationConfig, RegistrationFailure> {
        self.config()
            .registration
            .as_deref()
            .ok_or(RegistrationFailure::Disabled)
    }

    /// RFC 7591 section 3.1: register a client.
    ///
    /// `initial_access_token` is whatever the request presented as an RFC 6750 bearer token, and
    /// is passed straight to the host's [`RegistrationPolicy`]; this crate does not interpret it.
    ///
    /// On success the returned [`ClientInformation`] carries the ONLY copy of the client secret
    /// (for a confidential registration) and of the RFC 7592 registration access token. Neither is
    /// recoverable afterwards, by the client or by the host: see the module docs.
    pub async fn register_dynamic_client(
        &self,
        metadata: &ClientMetadata,
        initial_access_token: Option<&str>,
    ) -> Result<ClientInformation, RegistrationFailure> {
        let config = self.registration_config()?;

        // The host decides, FIRST, before anything is validated or written. With no policy
        // installed the answer is no: see [`RegistrationPolicy`] and RFC 7591 section 5. The
        // refusal is deliberately indistinguishable from a bad initial access token, because a
        // policy that refuses on content should not confirm what content it dislikes.
        let attempt = RegistrationAttempt {
            initial_access_token,
            metadata,
        };
        match self.hooks().registration_policy() {
            Some(policy) if policy.authorize(&attempt) == RegistrationDecision::Allow => {}
            _ => return Err(RegistrationFailure::Unauthorized),
        }

        let registered = validate(metadata, config)?;

        let now = crate::server::unix_seconds(self.now());
        let client_id = ClientId::new(crate::server::random_hex(16));
        let secret = (registered.token_endpoint_auth_method != AUTH_METHOD_NONE)
            .then(|| crate::server::random_hex(32));
        let secret_expires_at = secret.as_ref().map(|_| match config.client_secret_ttl {
            // RFC 7591 section 3.2.1: 0 means the secret never expires.
            None => 0,
            Some(ttl) => now.unwrap_or_default() + ttl.as_secs(),
        });
        let registration_access_token = crate::server::random_hex(32);

        let client = Client {
            client_id: client_id.clone(),
            auth: match &secret {
                None => ClientAuth::Public,
                Some(s) => ClientAuth::ConfidentialSecretHash {
                    hash: SecretHash::sha256(s),
                },
            },
            grant_types: registered.grant_types.clone(),
            redirect_uris: registered.redirect_uris.clone(),
            allowed_scopes: registered.scope.clone(),
            default_scopes: registered.scope.clone(),
            name: registered.client_name.clone(),
            registration: Some(Box::new(DynamicRegistration {
                registration_access_token_hash: SecretHash::sha256(&registration_access_token),
                client_id_issued_at: now,
                client_secret_expires_at: secret_expires_at,
                token_endpoint_auth_method: registered.token_endpoint_auth_method.clone(),
            })),
        };
        self.store()
            .put_client(client)
            .await
            .map_err(RegistrationFailure::Storage)?;

        // Emitted AFTER the write: a registration that failed to persist did not happen.
        self.hooks()
            .emit(|| crate::events::Event::ClientRegistered {
                client_id: client_id.as_str(),
            });

        Ok(ClientInformation {
            client_id: client_id.as_str().to_string(),
            client_secret: secret,
            client_id_issued_at: now,
            client_secret_expires_at: secret_expires_at,
            registration_access_token: config
                .management_enabled
                .then_some(registration_access_token),
            registration_client_uri: config
                .management_enabled
                .then(|| registration_client_uri(config, self.config(), client_id.as_str())),
            metadata: ClientMetadata {
                redirect_uris: registered.redirect_uris,
                token_endpoint_auth_method: Some(registered.token_endpoint_auth_method),
                grant_types: Some(
                    registered
                        .grant_types
                        .iter()
                        .map(|g| g.to_string())
                        .collect(),
                ),
                response_types: Some(registered.response_types),
                client_name: registered.client_name,
                scope: (!registered.scope.is_empty()).then(|| registered.scope.to_string()),
                software_statement: None,
            },
        })
    }

    /// Authenticate an RFC 7592 section 2 management request.
    ///
    /// Every failure is [`RegistrationFailure::Unauthorized`]: an unknown client, a statically
    /// provisioned client that has no registration access token, and a wrong token are one answer
    /// on the wire, because telling them apart is an enumeration oracle over the client table.
    ///
    /// The same timing caveat that `AuthorizationServer::authenticate_client` documents applies:
    /// this returns before any hashing when the client is unknown, so an unknown client and a
    /// known client with the wrong token are distinguishable by wall time. The comparison itself
    /// leaks nothing (see [`SecretHash::verify`]); equalising the two paths is the host's business,
    /// and this is a management endpoint the host is expected to throttle anyway.
    async fn authenticate_registration(
        &self,
        client_id: &ClientId,
        registration_access_token: &str,
    ) -> Result<(std::sync::Arc<Client>, DynamicRegistration), RegistrationFailure> {
        let config = self.registration_config()?;
        if !config.management_enabled {
            return Err(RegistrationFailure::Disabled);
        }
        let client = self
            .store()
            .get_client(client_id)
            .await
            .map_err(RegistrationFailure::Storage)?
            .ok_or(RegistrationFailure::Unauthorized)?;
        let registration = client
            .registration
            .as_deref()
            .cloned()
            .ok_or(RegistrationFailure::Unauthorized)?;
        if !registration
            .registration_access_token_hash
            .verify(registration_access_token, self.hooks().secret_verifier())
        {
            return Err(RegistrationFailure::Unauthorized);
        }
        Ok((client, registration))
    }

    /// RFC 7592 section 2.1: read a registration.
    ///
    /// The response carries no `client_secret` and no `registration_access_token`, because this
    /// server stores neither: see the module docs.
    pub async fn read_registration(
        &self,
        client_id: &ClientId,
        registration_access_token: &str,
    ) -> Result<ClientInformation, RegistrationFailure> {
        let (client, registration) = self
            .authenticate_registration(client_id, registration_access_token)
            .await?;
        let config = self.registration_config()?;
        Ok(ClientInformation {
            client_id: client.client_id.as_str().to_string(),
            client_secret: None,
            client_id_issued_at: registration.client_id_issued_at,
            client_secret_expires_at: registration.client_secret_expires_at,
            registration_access_token: None,
            registration_client_uri: Some(registration_client_uri(
                config,
                self.config(),
                client.client_id.as_str(),
            )),
            metadata: registered_metadata(&client, &registration),
        })
    }

    /// RFC 7592 section 2.2: replace a registration's metadata.
    ///
    /// The whole document is replaced, not merged: section 2.2 says the client sends its full
    /// metadata and that any omitted member is treated as absent. Merging would make a client that
    /// dropped a redirect URI keep it, which is precisely backwards for the one member that
    /// decides where a code may be delivered.
    ///
    /// `client_id` cannot be changed (section 2.2), and the grant and scope ceilings of
    /// [`RegistrationConfig`] apply again, so an update cannot reach anything a fresh registration
    /// could not.
    pub async fn update_registration(
        &self,
        client_id: &ClientId,
        registration_access_token: &str,
        metadata: &ClientMetadata,
    ) -> Result<ClientInformation, RegistrationFailure> {
        let (client, registration) = self
            .authenticate_registration(client_id, registration_access_token)
            .await?;
        let config = self.registration_config()?;

        // The host decides on an UPDATE too, and this is not a formality. RFC 7592 section 2.2
        // has the client send a complete replacement metadata document, so every content control
        // a policy applied at registration (a `client_name` impersonating the deployment, a
        // `redirect_uris` entry on a domain the host will not serve) is exactly what this call can
        // rewrite. Consulting the policy only on the way in would leave every one of those
        // controls one PUT away from being void, and the registration access token is long lived
        // where an initial access token is typically single use.
        //
        // `initial_access_token` is None because there is no second one to present: RFC 7592
        // section 2 authenticates this request with the registration access token, which
        // `authenticate_registration` above has already verified.
        let attempt = RegistrationAttempt {
            initial_access_token: None,
            metadata,
        };
        match self.hooks().registration_policy() {
            Some(policy) if policy.authorize(&attempt) == RegistrationDecision::Allow => {}
            _ => return Err(RegistrationFailure::Unauthorized),
        }

        let registered = validate(metadata, config)?;

        // A change of authentication method that needs a secret the registration does not have
        // mints one; this is the only path other than registration itself that can produce one.
        let wants_secret = registered.token_endpoint_auth_method != AUTH_METHOD_NONE;
        let had_secret = client.auth.is_confidential();
        let new_secret = (wants_secret && !had_secret).then(|| crate::server::random_hex(32));
        let auth = match (&new_secret, wants_secret) {
            (Some(s), _) => ClientAuth::ConfidentialSecretHash {
                hash: SecretHash::sha256(s),
            },
            // Keeps the existing verifier: this server cannot re-issue a secret it does not hold,
            // and silently rotating one on every metadata edit would log the client out of the
            // token endpoint for changing its name.
            (None, true) => client.auth.clone(),
            (None, false) => ClientAuth::Public,
        };
        let client_secret_expires_at = match (&new_secret, wants_secret) {
            (Some(_), _) => Some(match config.client_secret_ttl {
                None => 0,
                Some(ttl) => {
                    crate::server::unix_seconds(self.now()).unwrap_or_default() + ttl.as_secs()
                }
            }),
            (None, true) => registration.client_secret_expires_at,
            (None, false) => None,
        };

        let updated_registration = DynamicRegistration {
            registration_access_token_hash: registration.registration_access_token_hash.clone(),
            client_id_issued_at: registration.client_id_issued_at,
            client_secret_expires_at,
            token_endpoint_auth_method: registered.token_endpoint_auth_method.clone(),
        };
        let updated = Client {
            client_id: client.client_id.clone(),
            auth,
            grant_types: registered.grant_types.clone(),
            redirect_uris: registered.redirect_uris.clone(),
            allowed_scopes: registered.scope.clone(),
            default_scopes: registered.scope.clone(),
            name: registered.client_name.clone(),
            registration: Some(Box::new(updated_registration.clone())),
        };
        self.store()
            .put_client(updated.clone())
            .await
            .map_err(RegistrationFailure::Storage)?;
        self.hooks()
            .emit(|| crate::events::Event::ClientRegistrationUpdated {
                client_id: client_id.as_str(),
            });

        Ok(ClientInformation {
            client_id: client_id.as_str().to_string(),
            client_secret: new_secret,
            client_id_issued_at: updated_registration.client_id_issued_at,
            client_secret_expires_at: updated_registration.client_secret_expires_at,
            registration_access_token: None,
            registration_client_uri: Some(registration_client_uri(
                config,
                self.config(),
                client_id.as_str(),
            )),
            metadata: registered_metadata(&updated, &updated_registration),
        })
    }

    /// RFC 7592 section 2.3: delete a registration.
    ///
    /// Deletion takes everything the registration was issued with it, through
    /// [`Storage::delete_client`]: a client that no longer exists must not still have live access
    /// tokens, refresh chains or outstanding authorization codes. Section 2.3 requires exactly
    /// that, and it is the half of deletion that is easy to skip and impossible to notice.
    pub async fn delete_registration(
        &self,
        client_id: &ClientId,
        registration_access_token: &str,
    ) -> Result<(), RegistrationFailure> {
        self.authenticate_registration(client_id, registration_access_token)
            .await?;
        self.store()
            .delete_client(client_id)
            .await
            .map_err(RegistrationFailure::Storage)?;
        self.hooks()
            .emit(|| crate::events::Event::ClientRegistrationDeleted {
                client_id: client_id.as_str(),
            });
        Ok(())
    }
}

/// RFC 7592 section 3 `registration_client_uri`: `{registration_endpoint}/{client_id}`.
///
/// The client id is a 32-character hex string this server minted, so it carries nothing that needs
/// percent-encoding here.
fn registration_client_uri(
    config: &RegistrationConfig,
    server: &ServerConfig,
    client_id: &str,
) -> String {
    format!("{}/{client_id}", config.endpoint(&server.issuer))
}

#[cfg(test)]
#[path = "tests/registration.rs"]
mod tests;