Time-limited access
Banks and regulated businesses often need access that’s temporary: an approver covering leave, a contractor until the end of the month, an auditor for one week.
Grant it
Section titled “Grant it”err := w.Assign(ctx, "tolu", "loan_officer", lagos, wardn.From(time.Date(2026, 11, 9, 9, 0, 0, 0, lagosTZ)), wardn.Until(time.Date(2026, 11, 13, 17, 0, 0, 0, lagosTZ)))When your code computes the window, use wardn’s clock, so tests that move it move yours too:
err := w.Assign(ctx, doctor, "doctor", cardiology, wardn.Until(w.Now().Add(72*time.Hour)))Fromalone: access starts later and never ends.Untilalone: access starts now and ends later.- The window includes its start and excludes its end: at exactly 17:00 on the 13th, access is gone.
- Assigning again replaces the window, for example to extend it, or to make it permanent by passing neither option.
- Windows must end after they start, may not already have ended, and must fall within years 1 to 9999.
No cleanup job required
Section titled “No cleanup job required”The window is checked on every request against the current time, so access stops at exactly the right moment, even if nothing ever deletes the assignment. This also holds with caching: wardn caches the assignment, never the answer.
Record expiries in your audit trail
Section titled “Record expiries in your audit trail”Regulators expect a record of when access ended. The expiry sweeper reports each expired assignment through your audit hook as an assignment.expired event:
// Run in its own goroutine; safe on every instance.go w.RunExpiry(ctx, time.Minute, func(err error) { log.Error("expiry sweep", "err", err) })Or call w.SweepExpired(ctx) from your own scheduler. Pass wardn.RemoveExpired() to also delete expired assignments once they’re reported.
If your audit hook fails, the sweep stops, and those expiries are reported again next time. An expiry can be reported more than once (after a failure, or when several instances sweep at the same moment), so deduplicate on user, role, scope and expires_at if your trail needs exactly one record. An assignment that was granted again while a sweep was running is never removed.
Testing with time
Section titled “Testing with time”Use a fake clock to move time forward in tests:
clock := wardntest.NewClock(time.Date(2026, 11, 9, 9, 0, 0, 0, time.UTC))w := wardntest.New(t, "wardn.yaml", wardntest.WithClock(clock))
// ... assign with Until(...)clock.Advance(48 * time.Hour)// ... check that access has endedServer clocks
Section titled “Server clocks”Each server checks time windows against its own clock. Nothing it shares with other servers (the database or the cache) carries a time or a decision. So a server whose clock is off is wrong by exactly its skew and no longer. If a clock is 2 seconds slow, access starts 2 seconds late and ends 2 seconds late on that server, and on time everywhere else.
Questions about this page?
Ask below, point out something wrong or unclear, or suggest an improvement. Reading needs no account; to post, sign in with GitHub.