Skip to content

feat(identity): Implement user password change endpoint#970

Merged
gtema merged 1 commit into
mainfrom
claude/scim-v2-support-adr-u1f488
Jul 10, 2026
Merged

feat(identity): Implement user password change endpoint#970
gtema merged 1 commit into
mainfrom
claude/scim-v2-support-adr-u1f488

Conversation

@gtema

@gtema gtema commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

What

Implements for changing user passwords.

How

  • Unauthenticated endpoint that verifies the original password via authenticate_by_password before calling update_user_password
  • Returns 401 on auth failure, 409 for nonlocal users, 204 on success
  • 4 unit tests covering all response codes

Bug Fixes

Also fixes Conflict error mapping bugs in error_conv.rs:

Provider Error Before After
ResourceProviderError::Conflict 400 409
RevokeProviderError::Conflict 400 409
IdentityProviderError::Conflict 500 409
TokenProviderError::Conflict 500 409

Nonlocal user password update now returns 409 (was 404).

Files Changed

  • crates/api-types/src/error_conf.rs: 4 Conflict mappings fixed
  • crates/api-types/src/v3/user.rs: UserPasswordRequest API types
  • crates/identity-driver-sql/src/local_user.rs: nonlocal → 409
  • crates/identity-driver-sql/src/lib.rs: simplified password update
  • crates/keystone/src/api/v3/user/password.rs: new handler + tests
  • crates/keystone/src/api/v3/user/mod.rs: route registration

@gtema
gtema force-pushed the claude/scim-v2-support-adr-u1f488 branch from 3f68784 to b5885dd Compare July 9, 2026 17:41
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦢 Load Test Results

Goose Attack Report

Plan Overview

Action Started Stopped Elapsed Users
Increasing 26-07-10 13:36:03 26-07-10 13:36:19 00:00:16 0 → 30
Maintaining 26-07-10 13:36:19 26-07-10 13:36:49 00:00:30 30
Decreasing 26-07-10 13:36:49 26-07-10 13:37:48 00:00:59 0 ← 30

Request Metrics

Method Name # Requests # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
DELETE DELETE /v3/auth/tokens 514 0 104.96 9 141 17.13 0.00
DELETE DELETE /v3/projects/:id (teardown) 2 0 67.50 66 69 0.07 0.00
DELETE DELETE /v3/users/:id (teardown) 3 0 61.67 38 76 0.10 0.00
GET 4751 0 98.90 67 193 158.37 0.00
GET GET /v3/auth/tokens (validate new) 512 1 222.19 40 60001 17.07 0.03
GET GET /v3/projects/:id 742 0 80.59 68 113 24.73 0.00
GET GET /v3/projects/:id (catalog) 736 0 80.82 67 114 24.53 0.00
GET GET /v3/users/:id 1034 0 86.86 74 119 34.47 0.00
GET GET /v3/users/:id (catalog) 812 0 86.69 73 123 27.07 0.00
POST POST /v3/auth/tokens 511 0 79.39 65 104 17.03 0.00
Aggregated 9617 1 99.61 9 60001 320.57 0.03

Response Time Metrics

Method Name 50%ile (ms) 60%ile (ms) 70%ile (ms) 80%ile (ms) 90%ile (ms) 95%ile (ms) 99%ile (ms) 100%ile (ms)
DELETE DELETE /v3/auth/tokens 110 110 110 110 110 120 130 140
DELETE DELETE /v3/projects/:id (teardown) 66 66 66 69 69 69 69 69
DELETE DELETE /v3/users/:id (teardown) 71 71 71 71 76 76 76 76
GET 90 95 99 110 150 160 170 190
GET GET /v3/auth/tokens (validate new) 110 110 110 110 110 120 120 60,000
GET GET /v3/projects/:id 80 81 83 84 87 89 94 110
GET GET /v3/projects/:id (catalog) 81 82 83 84 87 90 94 110
GET GET /v3/users/:id 86 88 89 91 93 96 100 119
GET GET /v3/users/:id (catalog) 86 87 89 90 93 96 100 120
POST POST /v3/auth/tokens 79 80 81 83 85 87 93 100
Aggregated 87 90 96 100 110 150 160 60,000

Status Code Metrics

Method Name Status Codes
DELETE DELETE /v3/auth/tokens 514 [204]
DELETE DELETE /v3/projects/:id (teardown) 2 [204]
DELETE DELETE /v3/users/:id (teardown) 3 [204]
GET 4,751 [200]
GET GET /v3/auth/tokens (validate new) 511 [200], 1 [0]
GET GET /v3/projects/:id 742 [200]
GET GET /v3/projects/:id (catalog) 736 [200]
GET GET /v3/users/:id 1,034 [200]
GET GET /v3/users/:id (catalog) 812 [200]
POST POST /v3/auth/tokens 511 [200]
Aggregated 1 [0], 9,097 [200], 519 [204]

Transaction Metrics

Transaction # Times Run # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
ReadHeavy
0.0 1 0 41.00 41 41 0.03 0.00
0.1 807 0 98.15 75 141 26.90 0.00
0.2 806 0 81.31 67 105 26.87 0.00
0.3 806 0 80.82 69 115 26.87 0.00
TokenLifecycle
1.0 0 0 0.00 0 0 0.00 0.00
1.1 514 0 407.14 118 60091 17.13 0.00
ValidateToken
2.0 0 0 0.00 0 0 0.00 0.00
2.1 783 0 153.62 120 193 26.10 0.00
UserCRUD
3.0 0 0 0.00 0 0 0.00 0.00
3.1 0 0 0.00 0 0 0.00 0.00
3.2 1034 0 86.91 74 119 34.47 0.00
3.3 3 0 61.67 38 76 0.10 0.00
ProjectCRUD
4.0 0 0 0.00 0 0 0.00 0.00
4.1 0 0 0.00 0 0 0.00 0.00
4.2 742 0 80.63 68 113 24.73 0.00
4.3 2 0 67.50 66 69 0.07 0.00
UserRead
5.0 0 0 0.00 0 0 0.00 0.00
5.1 813 0 97.95 79 124 27.10 0.00
5.2 812 0 86.75 73 123 27.07 0.00
ProjectRead
6.0 0 0 0.00 0 0 0.00 0.00
6.1 736 0 81.91 70 119 24.53 0.00
6.2 736 0 80.88 68 114 24.53 0.00
Aggregated 8595 0 111.46 38 60091 286.50 0.00

Scenario Metrics

Transaction # Users # Times Run Average (ms) Min (ms) Max (ms) Scenarios/s Iterations
ReadHeavy 7 803 261.37 233 303 26.77 114.71
TokenLifecycle 5 509 291.75 267 331 16.97 101.80
ValidateToken 4 779 153.74 135 193 25.97 194.75
UserCRUD 3 1031 86.94 74 119 34.37 343.67
ProjectCRUD 2 740 80.66 68 113 24.67 370.00
UserRead 5 810 185.28 166 225 27.00 162.00
ProjectRead 4 732 163.40 141 202 24.40 183.00
Aggregated 30 5404 166.02 68 331 180.13 1469.93

Error Metrics

Method Name # Error
GET GET /v3/auth/tokens (validate new) 1 error sending request GET /v3/auth/tokens (validate new): operation timed out

View full report

@gtema
gtema force-pushed the claude/scim-v2-support-adr-u1f488 branch 2 times, most recently from 2337c5e to b224a03 Compare July 9, 2026 17:59
@gtema gtema changed the title feat(identity): implement user password change endpoint feat(identity): Implement user password change endpoint Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchclaude/scim-v2-support-adr-u1f488
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
nanoseconds (ns)
(Result Δ%)
Upper Boundary
nanoseconds (ns)
(Limit %)
Command_Serde/apply/remove📈 view plot
🚷 view threshold
142,090.00 ns
(-44.26%)Baseline: 254,897.50 ns
1,513,977.10 ns
(9.39%)
Command_Serde/apply/set📈 view plot
🚷 view threshold
156,340.00 ns
(-28.71%)Baseline: 219,286.94 ns
813,472.46 ns
(19.22%)
Command_Serde/pack/delete📈 view plot
🚷 view threshold
122.81 ns
(-0.03%)Baseline: 122.84 ns
144.60 ns
(84.93%)
Command_Serde/pack/delete_index📈 view plot
🚷 view threshold
110.65 ns
(-0.32%)Baseline: 111.00 ns
130.44 ns
(84.83%)
Command_Serde/pack/set📈 view plot
🚷 view threshold
204.95 ns
(+3.15%)Baseline: 198.69 ns
237.61 ns
(86.25%)
Command_Serde/pack/set_index📈 view plot
🚷 view threshold
110.28 ns
(-0.40%)Baseline: 110.72 ns
129.64 ns
(85.06%)
Command_Serde/unpack/delete📈 view plot
🚷 view threshold
188.37 ns
(-3.30%)Baseline: 194.79 ns
233.79 ns
(80.57%)
Command_Serde/unpack/delete_index📈 view plot
🚷 view threshold
160.92 ns
(-0.97%)Baseline: 162.50 ns
198.38 ns
(81.12%)
Command_Serde/unpack/set📈 view plot
🚷 view threshold
275.39 ns
(+1.40%)Baseline: 271.58 ns
324.40 ns
(84.89%)
Command_Serde/unpack/set_index📈 view plot
🚷 view threshold
160.54 ns
(-0.46%)Baseline: 161.27 ns
194.65 ns
(82.47%)
Payload_encryption/pack/remove_cmd📈 view plot
🚷 view threshold
114.88 ns
(-1.81%)Baseline: 117.00 ns
139.81 ns
(82.17%)
Payload_encryption/pack/set_cmd📈 view plot
🚷 view threshold
243.23 ns
(+17.76%)Baseline: 206.54 ns
262.72 ns
(92.58%)
Payload_encryption/unpack/remove_cmd📈 view plot
🚷 view threshold
204.95 ns
(-0.86%)Baseline: 206.73 ns
253.07 ns
(80.98%)
Payload_encryption/unpack/set_cmd📈 view plot
🚷 view threshold
304.27 ns
(+6.65%)Baseline: 285.31 ns
348.38 ns
(87.34%)
Raft_1Node_Latency/prefix/1node📈 view plot
🚷 view threshold
3,322,600.00 ns
(+19.27%)Baseline: 2,785,800.78 ns
6,182,740.73 ns
(53.74%)
Raft_1Node_Latency/read/1node📈 view plot
🚷 view threshold
43,442.00 ns
(+40.26%)Baseline: 30,971.71 ns
73,773.81 ns
(58.89%)
Raft_1Node_Latency/remove/1node📈 view plot
🚷 view threshold
423,060.00 ns
(-16.58%)Baseline: 507,129.53 ns
1,555,869.33 ns
(27.19%)
Raft_1Node_Latency/write/1node📈 view plot
🚷 view threshold
433,310.00 ns
(-21.83%)Baseline: 554,291.88 ns
1,867,429.17 ns
(23.20%)
build_snapshot/default📈 view plot
🚷 view threshold
121,920.00 ns
(+9.90%)Baseline: 110,934.11 ns
130,166.39 ns
(93.66%)
fernet token/project📈 view plot
🚷 view threshold
1,430.30 ns
(+2.26%)Baseline: 1,398.63 ns
1,629.57 ns
(87.77%)
get_data_keyspace📈 view plot
🚷 view threshold
0.32 ns
(+2.38%)Baseline: 0.32 ns
0.36 ns
(88.85%)
get_db📈 view plot
🚷 view threshold
0.32 ns
(+1.51%)Baseline: 0.32 ns
0.36 ns
(88.08%)
get_fernet_token_timestamp/project📈 view plot
🚷 view threshold
144.66 ns
(+1.54%)Baseline: 142.47 ns
174.79 ns
(82.76%)
get_keyspace📈 view plot
🚷 view threshold
4.41 ns
(-7.47%)Baseline: 4.77 ns
9.51 ns
(46.42%)
🐰 View full continuous benchmarking report in Bencher

@gtema
gtema force-pushed the claude/scim-v2-support-adr-u1f488 branch from b224a03 to bfc7160 Compare July 10, 2026 11:31
Add POST /v3/users/{user_id}/password handler that authenticates via
original_password then updates the password. The endpoint is unauthenticated;
the original password serves as credential verification.

Also fixes error mapping bugs:
- ResourceProviderError::Conflict now returns 409 (was 400)
- RevokeProviderError::Conflict now returns 409 (was 400)
- IdentityProviderError::Conflict now returns 409 (was 500)
- TokenProviderError::Conflict now returns 409 (was 500)
- Nonlocal user password update returns 409 (was 404)

Add unit tests for all Conflict mappings, UserPassword validation, and
password handler paths.

Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
@gtema
gtema force-pushed the claude/scim-v2-support-adr-u1f488 branch from bfc7160 to fd7e604 Compare July 10, 2026 13:20
@gtema
gtema enabled auto-merge (squash) July 10, 2026 13:45
@gtema
gtema merged commit ff5c245 into main Jul 10, 2026
32 checks passed
@gtema
gtema deleted the claude/scim-v2-support-adr-u1f488 branch July 10, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant