Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Realm setting for password rotation (#592)
Browse files Browse the repository at this point in the history
* new settings

* migration

* ahh

* full password

* negative margin

* margins

* less padding
  • Loading branch information
whaught authored Sep 19, 2020
1 parent f3073fb commit 44a56d0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 25 deletions.
87 changes: 73 additions & 14 deletions cmd/server/assets/realm.html
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,50 @@ <h1>Realm settings</h1>
</select>
</div>
</div>

<div class="form-group row mb-n3">
<label for="passwordRotate" class="col-sm-3">Password rotation every:</label>
<div class="col-sm-2">
<select class="form-control" name="passwordRotate" id="passwordRotate">
{{$current := $realm.PasswordRotationPeriodDays}}
{{range $prd := .passwordRotateDays}}
<option value="{{$prd}}" {{if (eq $prd $current)}}selected{{end}}>{{if (eq $prd 0)}}Off{{else}}{{$prd}}{{end}}</option>
{{end}}
</select>
</div>
<div class="input-group-append">
<label class="ml-1" for="passwordWarn">days</label>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-3"></div>
<small class="form-text text-muted col">
The server will require the user to change their password after this number of
days elapse since their last password change.
</small>
</div>

<div class="form-group row mb-n3">
<label for="passwordRotate" class="col-sm-3">Password rotation warning:</label>
<div class="col-sm-2">
<select class="custom-select{{if $realm.ErrorsFor "passwordWarn"}} is-invalid{{end}}" name="passwordWarn" id="passwordWarn">
{{$current := $realm.PasswordRotationWarningDays}}
{{range $pwd := .passwordWarnDays}}
<option value="{{$pwd }}" {{if (eq $pwd $current)}}selected{{end}}>{{if (eq $pwd 0)}}Off{{else}}{{$pwd}}{{end}}</option>
{{end}}
</select>
</div>
<div class="input-group-append">
<label class="ml-1" for="passwordWarn">days before rotation</label>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-3"></div>
<small class="form-text text-muted col">
When the next password rotation is due in this many days, the server will warn the users
that their password will expire soon.
</small>
</div>
</div>
</div>

Expand Down Expand Up @@ -231,9 +275,9 @@ <h1>Realm settings</h1>
</div>
</div>

<div class="form-group row">
<div class="form-group row mb-n1">
<label for="codeLength" class="col-sm-3">Code expires after:</label>
<div class="col-sm-8">
<div class="col-sm-2">
{{if $realm.EnableENExpress}}
<input class="form-control{{if $realm.ErrorsFor "CodeDurationSeconds"}} is-invalid{{end}}" name="codeDuration" id="codeDuration" type="text" value="{{$realm.GetCodeDurationMinutes}}" readonly />
{{else}}
Expand All @@ -243,15 +287,23 @@ <h1>Realm settings</h1>
<option value="{{$scm}}" {{if (eq $scm $current)}}selected{{end}}>{{$scm}}</option>
{{end}}
</select>
<small class="form-text text-muted">
The short code can be valid from anywhere between <code>5</code> and <code>60</code>
minutes. If you are using SMS deeplinks, it is recommended to keep this duration
short and let the long code be valid for a longer period (up to <code>24</code> hours).
</small>
{{end}}
</div>
<div class="col-sm-1">minutes</div>
<div class="input-group-append">
<label class="ml-1" for="passwordWarn">minutes</label>
</div>
</div>
{{if not $realm.EnableENExpress}}
<div class="row mb-3">
<div class="col-sm-3"></div>
<small class="form-text text-muted col">
The short code can be valid from anywhere between <code>5</code> and <code>60</code>
minutes. If you are using SMS deeplinks, it is recommended to keep this duration
short and let the long code be valid for a longer period (up to <code>24</code> hours).
</small>
</div>
{{end}}


<div class="form-group row">
<label for="codeLength" class="col-sm-3">Long code characters:</label>
Expand All @@ -273,9 +325,9 @@ <h1>Realm settings</h1>
</div>
</div>

<div class="form-group row">
<div class="form-group row mb-n3">
<label for="codeLength" class="col-sm-3">Long code expires after:</label>
<div class="col-sm-8">
<div class="col-sm-2">
{{if $realm.EnableENExpress}}
<input class="form-control{{if $realm.ErrorsFor "LongCodeDurationSeconds"}} is-invalid{{end}}" name="longCodeDuration" id="longCodeDuration" type="text" value="{{$realm.GetLongCodeDurationHours}}" readonly />
{{else}}
Expand All @@ -285,13 +337,20 @@ <h1>Realm settings</h1>
<option value="{{$lch}}" {{if (eq $lch $current)}}selected{{end}}>{{$lch}}</option>
{{end}}
</select>
<small class="form-text text-muted">
The long code can be valid between <code>1</code> and <code>24</code> hours.
</small>
{{end}}
</div>
<div class="col-sm-1">hours</div>
<div class="input-group-append">
<label class="ml-1 col" for="passwordWarn">hours</label>
</div>
</div>
{{if not $realm.EnableENExpress}}
<div class="row mb-3">
<div class="col-sm-3"></div>
<small class="form-text text-muted col">
The long code can be valid between <code>1</code> and <code>24</code> hours.
</small>
</div>
{{end}}

<div class="form-group row">
<label for="name" class="col-sm-3">SMS text template:</label>
Expand Down
32 changes: 21 additions & 11 deletions pkg/controller/realmadmin/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import (
)

var (
shortCodeLengths = []int{6, 7, 8}
shortCodeMinutes = []int{}
longCodeLengths = []int{12, 13, 14, 15, 16}
longCodeHours = []int{}
shortCodeLengths = []int{6, 7, 8}
shortCodeMinutes = []int{}
longCodeLengths = []int{12, 13, 14, 15, 16}
longCodeHours = []int{}
passwordRotationPeriodDays = []int{0, 30, 60, 90, 365}
passwordRotationWarningDays = []int{0, 1, 3, 5, 7, 30}
)

func init() {
Expand All @@ -44,13 +46,15 @@ func init() {

func (c *Controller) HandleSave() http.Handler {
type FormData struct {
Name string `form:"name"`
RegionCode string `form:"regionCode"`
AllowedTestTypes database.TestType `form:"allowedTestTypes"`
MFAMode int16 `form:"MFAMode"`
EmailVerifiedMode int16 `form:"emailVerifiedMode"`
RequireDate bool `form:"requireDate"`
WelcomeMessage string `form:"welcomeMessage"`
Name string `form:"name"`
RegionCode string `form:"regionCode"`
AllowedTestTypes database.TestType `form:"allowedTestTypes"`
MFAMode int16 `form:"MFAMode"`
EmailVerifiedMode int16 `form:"emailVerifiedMode"`
PasswordRotateDays uint `form:"passwordRotate"`
PasswordWarnDays uint `form:"passwordWarn"`
RequireDate bool `form:"requireDate"`
WelcomeMessage string `form:"welcomeMessage"`

CodeLength uint `form:"codeLength"`
CodeDurationMinutes int64 `form:"codeDuration"`
Expand Down Expand Up @@ -111,6 +115,8 @@ func (c *Controller) HandleSave() http.Handler {
realm.SMSTextTemplate = form.SMSTextTemplate
realm.MFAMode = database.AuthRequirement(form.MFAMode)
realm.EmailVerifiedMode = database.AuthRequirement(form.EmailVerifiedMode)
realm.PasswordRotationPeriodDays = form.PasswordRotateDays
realm.PasswordRotationWarningDays = form.PasswordWarnDays
realm.AbusePreventionEnabled = form.AbusePreventionEnabled
realm.AbusePreventionLimitFactor = form.AbusePreventionLimitFactor
if err := c.db.SaveRealm(realm); err != nil {
Expand Down Expand Up @@ -208,6 +214,10 @@ func (c *Controller) renderShow(ctx context.Context, w http.ResponseWriter, r *h
"likely": database.TestTypeConfirmed | database.TestTypeLikely,
"negative": database.TestTypeConfirmed | database.TestTypeLikely | database.TestTypeNegative,
}
// Valid settings for pwd rotation.
m["passwordRotateDays"] = passwordRotationPeriodDays
m["passwordWarnDays"] = passwordRotationWarningDays

// Valid settings for code parameters.
m["shortCodeLengths"] = shortCodeLengths
m["shortCodeMinutes"] = shortCodeMinutes
Expand Down
31 changes: 31 additions & 0 deletions pkg/database/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,37 @@ func (db *Database) getMigrations(ctx context.Context) *gormigrate.Gormigrate {
return tx.Exec("ALTER TABLE users DROP COLUMN IF EXISTS last_password_change").Error
},
},
{
ID: "00048-AddPasswordRotateToRealm",
Migrate: func(tx *gorm.DB) error {
sqls := []string{
`ALTER TABLE realms ADD COLUMN IF NOT EXISTS password_rotation_period_days integer NOT NULL DEFAULT 0`,
`ALTER TABLE realms ADD COLUMN IF NOT EXISTS password_rotation_warning_days integer NOT NULL DEFAULT 0`,
}

for _, sql := range sqls {
if err := tx.Exec(sql).Error; err != nil {
return err
}
}

return nil
},
Rollback: func(tx *gorm.DB) error {
sqls := []string{
`ALTER TABLE realms DROP COLUMN IF EXISTS password_rotation_period_days`,
`ALTER TABLE realms DROP COLUMN IF EXISTS password_rotation_warning_days `,
}

for _, sql := range sqls {
if err := tx.Exec(sql).Error; err != nil {
return err
}
}

return nil
},
},
})
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ type Realm struct {
// EmailVerifiedMode represents the mode for email verification requirements for the realm.
EmailVerifiedMode AuthRequirement `gorm:"type:smallint; not null; default: 0"`

// PasswordRotationPeriodDays is the number of days before the user must
// rotate their password.
PasswordRotationPeriodDays uint `gorm:"type:smallint; not null; default: 0"`

// PasswordRotationWarningDays is the number of days before Password expiry
// that the user should receive a warning.
PasswordRotationWarningDays uint `gorm:"type:smallint; not null; default: 0"`

// AllowedTestTypes is the type of tests that this realm permits. The default
// value is to allow all test types.
AllowedTestTypes TestType `gorm:"type:smallint; not null; default: 14"`
Expand Down Expand Up @@ -209,6 +217,10 @@ func (r *Realm) BeforeSave(tx *gorm.DB) error {
}
}

if r.PasswordRotationWarningDays > r.PasswordRotationPeriodDays {
r.AddError("passwordWarn", "may not be longer than password rotation period")
}

if r.CodeLength < 6 {
r.AddError("codeLength", "must be at least 6")
}
Expand Down

0 comments on commit 44a56d0

Please sign in to comment.