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
8 changes: 8 additions & 0 deletions crates/policy/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ pub enum Code {
/// The username contains only numeric characters.
UsernameAllNumeric,

/// The username is banned.
UsernameBanned,

/// The username is not allowed.
UsernameNotAllowed,

/// The email domain is not allowed.
EmailDomainNotAllowed,

Expand All @@ -54,6 +60,8 @@ impl Code {
Self::UsernameTooLong => "username-too-long",
Self::UsernameInvalidChars => "username-invalid-chars",
Self::UsernameAllNumeric => "username-all-numeric",
Self::UsernameBanned => "username-banned",
Self::UsernameNotAllowed => "username-not-allowed",
Self::EmailDomainNotAllowed => "email-domain-not-allowed",
Self::EmailDomainBanned => "email-domain-banned",
Self::EmailNotAllowed => "email-not-allowed",
Expand Down
29 changes: 29 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,35 @@ policy:
# don't require clients to provide a client_uri. default: false
allow_missing_client_uri: false

# Restrictions on user registration
registration:
# If specified, the username (localpart) *must* match one of the allowed
# usernames. If unspecified, all usernames are allowed.
allowed_usernames:
# Exact usernames that are allowed
literals: ["alice", "bob"]
# Substrings that match allowed usernames
substrings: ["user"]
# Regular expressions that match allowed usernames
regexes: ["^[a-z]+$"]
# Prefixes that match allowed usernames
prefixes: ["user-"]
# Suffixes that match allowed usernames
suffixes: ["-corp"]
# If specified, the username (localpart) *must not* match one of the
# banned usernames. If unspecified, all usernames are allowed.
banned_usernames:
# Exact usernames that are banned
literals: ["admin", "root"]
# Substrings that match banned usernames
substrings: ["admin", "root"]
# Regular expressions that match banned usernames
regexes: ["^admin$", "^root$"]
# Prefixes that match banned usernames
prefixes: ["admin-", "root-"]
# Suffixes that match banned usernames
suffixes: ["-admin", "-root"]

# Restrict what email addresses can be added to a user
emails:
# If specified, the email address *must* match one of the allowed addresses.
Expand Down
22 changes: 22 additions & 0 deletions policies/register/register.rego
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ allow if {
count(violation) == 0
}

username_allowed if {
not data.registration.allowed_usernames
}

username_allowed if {
common.matches_string_constraints(input.username, data.registration.allowed_usernames)
}

# METADATA
# entrypoint: true
violation contains {"field": "username", "code": "username-too-short", "msg": "username too short"} if {
Expand All @@ -39,6 +47,20 @@ violation contains {
not regex.match(`^[a-z0-9.=_/-]+$`, input.username)
}

violation contains {
"field": "username", "code": "username-banned",
"msg": "username is banned",
} if {
common.matches_string_constraints(input.username, data.registration.banned_usernames)
}

violation contains {
"field": "username", "code": "username-not-allowed",
"msg": "username is not allowed",
} if {
not username_allowed
}

violation contains {"msg": "unspecified registration method"} if {
not input.registration_method
}
Expand Down
14 changes: 14 additions & 0 deletions policies/register/register_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ test_numeric_username if {
not register.allow with input as {"username": "1234", "registration_method": "upstream-oauth2"}
}

test_allowed_username if {
register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
with data.registration.allowed_usernames.literals as ["hello"]
not register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
with data.registration.allowed_usernames.literals as ["world"]
}

test_banned_username if {
not register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
with data.registration.banned_usernames.literals as ["hello"]
register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
with data.registration.banned_usernames.literals as ["world"]
}

test_ip_ban if {
not register.allow with input as {
"username": "hello",
Expand Down
4 changes: 4 additions & 0 deletions templates/components/field.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
{{ _("mas.errors.username_invalid_chars") }}
{% elif error.code == "username-all-numeric" %}
{{ _("mas.errors.username_all_numeric") }}
{% elif error.code == "username-banned" %}
{{ _("mas.errors.username_banned") }}
{% elif error.code == "username-not-allowed" %}
{{ _("mas.errors.username_not_allowed") }}
{% elif error.code == "email-domain-not-allowed" %}
{{ _("mas.errors.email_domain_not_allowed") }}
{% elif error.code == "email-domain-banned" %}
Expand Down
24 changes: 17 additions & 7 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,23 +299,23 @@
},
"denied_policy": "Denied by policy: %(policy)s",
"@denied_policy": {
"context": "components/errors.html:17:7-58, components/field.html:81:19-70"
"context": "components/errors.html:17:7-58, components/field.html:85:19-70"
},
"email_banned": "Email is banned by the server policy",
"@email_banned": {
"context": "components/field.html:79:19-47"
"context": "components/field.html:83:19-47"
},
"email_domain_banned": "Email domain is banned by the server policy",
"@email_domain_banned": {
"context": "components/field.html:75:19-54"
"context": "components/field.html:79:19-54"
},
"email_domain_not_allowed": "Email domain is not allowed by the server policy",
"@email_domain_not_allowed": {
"context": "components/field.html:73:19-59"
"context": "components/field.html:77:19-59"
},
"email_not_allowed": "Email is not allowed by the server policy",
"@email_not_allowed": {
"context": "components/field.html:77:19-52"
"context": "components/field.html:81:19-52"
},
"field_required": "This field is required",
"@field_required": {
Expand All @@ -327,7 +327,7 @@
},
"password_mismatch": "Password fields don't match",
"@password_mismatch": {
"context": "components/errors.html:13:7-40, components/field.html:84:17-50"
"context": "components/errors.html:13:7-40, components/field.html:88:17-50"
},
"rate_limit_exceeded": "You've made too many requests in a short period. Please wait a few minutes and try again.",
"@rate_limit_exceeded": {
Expand All @@ -337,10 +337,20 @@
"@username_all_numeric": {
"context": "components/field.html:71:19-55"
},
"username_banned": "Username is banned by the server policy",
"@username_banned": {
"context": "components/field.html:73:19-50",
"description": "Error message shown on registration, when the username matches a pattern that is banned by the server policy."
},
"username_invalid_chars": "Username contains invalid characters. Use lowercase letters, numbers, dashes and underscores only.",
"@username_invalid_chars": {
"context": "components/field.html:69:19-57"
},
"username_not_allowed": "Username is not allowed by the server policy",
"@username_not_allowed": {
"context": "components/field.html:75:19-55",
"description": "Error message shown on registration, when the username *does not match* any of the patterns that are allowed by the server policy."
},
"username_taken": "This username is already taken",
"@username_taken": {
"context": "components/field.html:62:17-47"
Expand Down Expand Up @@ -424,7 +434,7 @@
},
"or_separator": "Or",
"@or_separator": {
"context": "components/field.html:103:10-31",
"context": "components/field.html:107:10-31",
"description": "Separator between the login methods"
},
"policy_violation": {
Expand Down
Loading