Skip to content

Commit c6cf84c

Browse files
authored
Support for allowing/banning specific username patterns during registration (#4131)
2 parents 1b62792 + 70b5612 commit c6cf84c

File tree

6 files changed

+94
-7
lines changed

6 files changed

+94
-7
lines changed

crates/policy/src/model.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ pub enum Code {
3232
/// The username contains only numeric characters.
3333
UsernameAllNumeric,
3434

35+
/// The username is banned.
36+
UsernameBanned,
37+
38+
/// The username is not allowed.
39+
UsernameNotAllowed,
40+
3541
/// The email domain is not allowed.
3642
EmailDomainNotAllowed,
3743

@@ -54,6 +60,8 @@ impl Code {
5460
Self::UsernameTooLong => "username-too-long",
5561
Self::UsernameInvalidChars => "username-invalid-chars",
5662
Self::UsernameAllNumeric => "username-all-numeric",
63+
Self::UsernameBanned => "username-banned",
64+
Self::UsernameNotAllowed => "username-not-allowed",
5765
Self::EmailDomainNotAllowed => "email-domain-not-allowed",
5866
Self::EmailDomainBanned => "email-domain-banned",
5967
Self::EmailNotAllowed => "email-not-allowed",

docs/reference/configuration.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,35 @@ policy:
382382
# don't require clients to provide a client_uri. default: false
383383
allow_missing_client_uri: false
384384

385+
# Restrictions on user registration
386+
registration:
387+
# If specified, the username (localpart) *must* match one of the allowed
388+
# usernames. If unspecified, all usernames are allowed.
389+
allowed_usernames:
390+
# Exact usernames that are allowed
391+
literals: ["alice", "bob"]
392+
# Substrings that match allowed usernames
393+
substrings: ["user"]
394+
# Regular expressions that match allowed usernames
395+
regexes: ["^[a-z]+$"]
396+
# Prefixes that match allowed usernames
397+
prefixes: ["user-"]
398+
# Suffixes that match allowed usernames
399+
suffixes: ["-corp"]
400+
# If specified, the username (localpart) *must not* match one of the
401+
# banned usernames. If unspecified, all usernames are allowed.
402+
banned_usernames:
403+
# Exact usernames that are banned
404+
literals: ["admin", "root"]
405+
# Substrings that match banned usernames
406+
substrings: ["admin", "root"]
407+
# Regular expressions that match banned usernames
408+
regexes: ["^admin$", "^root$"]
409+
# Prefixes that match banned usernames
410+
prefixes: ["admin-", "root-"]
411+
# Suffixes that match banned usernames
412+
suffixes: ["-admin", "-root"]
413+
385414
# Restrict what email addresses can be added to a user
386415
emails:
387416
# If specified, the email address *must* match one of the allowed addresses.

policies/register/register.rego

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ allow if {
1414
count(violation) == 0
1515
}
1616

17+
username_allowed if {
18+
not data.registration.allowed_usernames
19+
}
20+
21+
username_allowed if {
22+
common.matches_string_constraints(input.username, data.registration.allowed_usernames)
23+
}
24+
1725
# METADATA
1826
# entrypoint: true
1927
violation contains {"field": "username", "code": "username-too-short", "msg": "username too short"} if {
@@ -39,6 +47,20 @@ violation contains {
3947
not regex.match(`^[a-z0-9.=_/-]+$`, input.username)
4048
}
4149

50+
violation contains {
51+
"field": "username", "code": "username-banned",
52+
"msg": "username is banned",
53+
} if {
54+
common.matches_string_constraints(input.username, data.registration.banned_usernames)
55+
}
56+
57+
violation contains {
58+
"field": "username", "code": "username-not-allowed",
59+
"msg": "username is not allowed",
60+
} if {
61+
not username_allowed
62+
}
63+
4264
violation contains {"msg": "unspecified registration method"} if {
4365
not input.registration_method
4466
}

policies/register/register_test.rego

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ test_numeric_username if {
7575
not register.allow with input as {"username": "1234", "registration_method": "upstream-oauth2"}
7676
}
7777

78+
test_allowed_username if {
79+
register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
80+
with data.registration.allowed_usernames.literals as ["hello"]
81+
not register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
82+
with data.registration.allowed_usernames.literals as ["world"]
83+
}
84+
85+
test_banned_username if {
86+
not register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
87+
with data.registration.banned_usernames.literals as ["hello"]
88+
register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
89+
with data.registration.banned_usernames.literals as ["world"]
90+
}
91+
7892
test_ip_ban if {
7993
not register.allow with input as {
8094
"username": "hello",

templates/components/field.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
{{ _("mas.errors.username_invalid_chars") }}
7070
{% elif error.code == "username-all-numeric" %}
7171
{{ _("mas.errors.username_all_numeric") }}
72+
{% elif error.code == "username-banned" %}
73+
{{ _("mas.errors.username_banned") }}
74+
{% elif error.code == "username-not-allowed" %}
75+
{{ _("mas.errors.username_not_allowed") }}
7276
{% elif error.code == "email-domain-not-allowed" %}
7377
{{ _("mas.errors.email_domain_not_allowed") }}
7478
{% elif error.code == "email-domain-banned" %}

translations/en.json

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,23 +299,23 @@
299299
},
300300
"denied_policy": "Denied by policy: %(policy)s",
301301
"@denied_policy": {
302-
"context": "components/errors.html:17:7-58, components/field.html:81:19-70"
302+
"context": "components/errors.html:17:7-58, components/field.html:85:19-70"
303303
},
304304
"email_banned": "Email is banned by the server policy",
305305
"@email_banned": {
306-
"context": "components/field.html:79:19-47"
306+
"context": "components/field.html:83:19-47"
307307
},
308308
"email_domain_banned": "Email domain is banned by the server policy",
309309
"@email_domain_banned": {
310-
"context": "components/field.html:75:19-54"
310+
"context": "components/field.html:79:19-54"
311311
},
312312
"email_domain_not_allowed": "Email domain is not allowed by the server policy",
313313
"@email_domain_not_allowed": {
314-
"context": "components/field.html:73:19-59"
314+
"context": "components/field.html:77:19-59"
315315
},
316316
"email_not_allowed": "Email is not allowed by the server policy",
317317
"@email_not_allowed": {
318-
"context": "components/field.html:77:19-52"
318+
"context": "components/field.html:81:19-52"
319319
},
320320
"field_required": "This field is required",
321321
"@field_required": {
@@ -327,7 +327,7 @@
327327
},
328328
"password_mismatch": "Password fields don't match",
329329
"@password_mismatch": {
330-
"context": "components/errors.html:13:7-40, components/field.html:84:17-50"
330+
"context": "components/errors.html:13:7-40, components/field.html:88:17-50"
331331
},
332332
"rate_limit_exceeded": "You've made too many requests in a short period. Please wait a few minutes and try again.",
333333
"@rate_limit_exceeded": {
@@ -337,10 +337,20 @@
337337
"@username_all_numeric": {
338338
"context": "components/field.html:71:19-55"
339339
},
340+
"username_banned": "Username is banned by the server policy",
341+
"@username_banned": {
342+
"context": "components/field.html:73:19-50",
343+
"description": "Error message shown on registration, when the username matches a pattern that is banned by the server policy."
344+
},
340345
"username_invalid_chars": "Username contains invalid characters. Use lowercase letters, numbers, dashes and underscores only.",
341346
"@username_invalid_chars": {
342347
"context": "components/field.html:69:19-57"
343348
},
349+
"username_not_allowed": "Username is not allowed by the server policy",
350+
"@username_not_allowed": {
351+
"context": "components/field.html:75:19-55",
352+
"description": "Error message shown on registration, when the username *does not match* any of the patterns that are allowed by the server policy."
353+
},
344354
"username_taken": "This username is already taken",
345355
"@username_taken": {
346356
"context": "components/field.html:62:17-47"
@@ -424,7 +434,7 @@
424434
},
425435
"or_separator": "Or",
426436
"@or_separator": {
427-
"context": "components/field.html:103:10-31",
437+
"context": "components/field.html:107:10-31",
428438
"description": "Separator between the login methods"
429439
},
430440
"policy_violation": {

0 commit comments

Comments
 (0)