Skip to content

Commit 3d56241

Browse files
committed
create: auth.Authentication interface
1 parent 51ec266 commit 3d56241

5 files changed

Lines changed: 70 additions & 54 deletions

File tree

auth/atrributes.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"net/http"
55
)
66

7-
// IsValid type of middleware for authKey and passKey
8-
// protected endpoint routing
9-
type IsValid interface {
7+
// Authentication interface for middleware using
8+
// authKey and passKey protected endpoint routes
9+
type Authentication interface {
1010
IsValid(http.Handler) http.Handler
1111
}

auth/authkey.go

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,32 @@ import (
1717
"github.com/go-chi/chi/v5"
1818
)
1919

20-
// Auth structure for authentication and credential management for
20+
// AuthKey structure for authentication and credential management for
2121
// authorized user acces to restricted content.
2222
//
2323
// The package supports path base (eg. /api/{apikey}/action) requests,
2424
// however the admin management routes require a header token:{apikey}
2525
// value be set to access the user management routes.
26-
type Auth struct {
26+
type AuthKey struct {
2727
path *string // user:key map file location; memory only when nil
2828
uMap map[string]string // apikey->user map
2929
mwUser struct{} // middleware transport chain key
3030
mu sync.Mutex // mutex for uMap concurrency protection
3131
silent bool // silent output after bootstrap ends
32-
admin string // admin name
32+
admin string // admin user name; admin
33+
hKey string // header key name; token
3334
}
3435

35-
// NewAuth configurator will initialize an *auth.Auth and populate the
36+
// NewAuthKey configurator will initialize an *auth.Auth and populate the
3637
// uMap from the disk when path is provided or will use a memory uMap when
3738
// nil is passed for path; configues the admin routes on a chi.Router
38-
func NewAuth(path *string, r *chi.Mux) *Auth {
39+
func NewAuthKey(path *string, r *chi.Mux) *AuthKey {
3940

4041
if r == nil {
4142
r = chi.NewMux()
4243
}
4344

44-
ak := new(Auth).Configure(path)
45+
ak := new(AuthKey).Configure(path)
4546
r.Route("/a", func(rx chi.Router) {
4647
rx.Use(ak.IsAdmin)
4748
rx.Get("/", ak.UserHandler())
@@ -57,7 +58,7 @@ func NewAuth(path *string, r *chi.Mux) *Auth {
5758

5859
// generateKey defines the key generation methodology
5960
// used for ApiKey generation; eg. 5aee4f739eb44c2c
60-
func (a *Auth) generateKey() string {
61+
func (a *AuthKey) generateKey() string {
6162

6263
var b [8]byte
6364
rand.Read(b[:])
@@ -66,13 +67,16 @@ func (a *Auth) generateKey() string {
6667
}
6768

6869
// Silent toggle; {default:on}
69-
func (a *Auth) Silent() *Auth { a.silent = !a.silent; return a }
70+
func (a *AuthKey) Silent() *AuthKey { a.silent = !a.silent; return a }
7071

7172
// Admin sets the admin name; {default:admin}
72-
func (a *Auth) Admin(admin string) *Auth { a.admin = admin; return a }
73+
func (a *AuthKey) Admin(name string) *AuthKey { a.admin = name; return a }
74+
75+
// HKey sets the header key name; {default:token}
76+
func (a *AuthKey) HKey(key string) *AuthKey { a.hKey = key; return a }
7377

7478
// User will set a manual user,key combination; key must 6 or more characters
75-
func (a *Auth) User(user, key string) *Auth {
79+
func (a *AuthKey) User(user, key string) *AuthKey {
7680
if len(user) > 0 && len(key) > 5 {
7781
a.mu.Lock()
7882
a.uMap[strings.ToLower(key)] = strings.ToLower(user)
@@ -83,7 +87,7 @@ func (a *Auth) User(user, key string) *Auth {
8387

8488
// Start automated authorization refreshing; useful on clusters which
8589
// share a common file or sync'd file system
86-
func (a *Auth) Start(ctx context.Context, refresh *time.Duration) {
90+
func (a *AuthKey) Start(ctx context.Context, refresh *time.Duration) {
8791

8892
if refresh == nil || *refresh == 0 {
8993
freq := time.Hour
@@ -103,18 +107,23 @@ func (a *Auth) Start(ctx context.Context, refresh *time.Duration) {
103107

104108
// Configure will populate uMap from disk and create a default
105109
// admin user when no current file exists (or path is file)
106-
func (a *Auth) Configure(path *string) *Auth {
110+
func (a *AuthKey) Configure(path *string) *AuthKey {
107111

108-
// set default
112+
// set path default
109113
if path != nil {
110114
a.path = path
111115
}
112116

113-
// set default
117+
// set admin default
114118
if len(a.admin) == 0 {
115119
a.Admin("admin")
116120
}
117121

122+
// set nKey default
123+
if len(a.hKey) == 0 {
124+
a.HKey("token")
125+
}
126+
118127
if a.refresh() == 0 {
119128
if key := a.add(a.admin); !a.silent {
120129
log.Printf("auth: add %s [%s]", a.admin, key)
@@ -125,7 +134,7 @@ func (a *Auth) Configure(path *string) *Auth {
125134
}
126135

127136
// refresh builds uMap from disk; user apikey
128-
func (a *Auth) refresh() (n int) {
137+
func (a *AuthKey) refresh() (n int) {
129138

130139
a.mu.Lock()
131140
defer a.mu.Unlock()
@@ -155,7 +164,7 @@ func (a *Auth) refresh() (n int) {
155164
}
156165

157166
// save uMap to disk; user apikey
158-
func (a *Auth) save() {
167+
func (a *AuthKey) save() {
159168

160169
if a.path != nil {
161170
f, err := os.Create(*a.path)
@@ -172,7 +181,7 @@ func (a *Auth) save() {
172181
}
173182

174183
// add user to uMap
175-
func (a *Auth) add(user string) string {
184+
func (a *AuthKey) add(user string) string {
176185

177186
user = strings.ToLower(user)
178187
key := a.generateKey()
@@ -186,7 +195,7 @@ func (a *Auth) add(user string) string {
186195
}
187196

188197
// delete user from uMap
189-
func (a *Auth) delete(user string) bool {
198+
func (a *AuthKey) delete(user string) bool {
190199

191200
user = strings.ToLower(user)
192201
if user != a.admin {
@@ -206,7 +215,7 @@ func (a *Auth) delete(user string) bool {
206215
}
207216

208217
// update a user apikey in uMap
209-
func (a *Auth) update(user string) (string, bool) {
218+
func (a *AuthKey) update(user string) (string, bool) {
210219

211220
if a.delete(user) {
212221
return a.add(user), true
@@ -216,7 +225,7 @@ func (a *Auth) update(user string) (string, bool) {
216225
}
217226

218227
// check the key in the uMap and returns the user and lookup status
219-
func (a *Auth) check(key string) (user string, ok bool) {
228+
func (a *AuthKey) check(key string) (user string, ok bool) {
220229

221230
if len(key) > 0 {
222231
a.mu.Lock()
@@ -235,7 +244,7 @@ func (a *Auth) check(key string) (user string, ok bool) {
235244
// AddHandler will add a new user to the ApiKey.uMap authority
236245
//
237246
// .../add/{user}
238-
func (a *Auth) AddHandler() http.HandlerFunc {
247+
func (a *AuthKey) AddHandler() http.HandlerFunc {
239248

240249
type response struct {
241250
Status int `json:"status"`
@@ -261,7 +270,7 @@ func (a *Auth) AddHandler() http.HandlerFunc {
261270
// DeleteHandler removes a user from the ApiKey.uMap authority
262271
//
263272
// .../remove/{user}
264-
func (a *Auth) DeleteHandler() http.HandlerFunc {
273+
func (a *AuthKey) DeleteHandler() http.HandlerFunc {
265274

266275
type response struct {
267276
Status int `json:"status"`
@@ -283,7 +292,7 @@ func (a *Auth) DeleteHandler() http.HandlerFunc {
283292
// UpdateHandler reloads the ApiKey.uMap from disk
284293
//
285294
// .../update/{user}
286-
func (a *Auth) UpdateHandler() http.HandlerFunc {
295+
func (a *AuthKey) UpdateHandler() http.HandlerFunc {
287296

288297
type response struct {
289298
Status int `json:"status"`
@@ -316,7 +325,7 @@ func (a *Auth) UpdateHandler() http.HandlerFunc {
316325
// RefreshHandler reloads the ApiKey.uMap from disk
317326
//
318327
// .../refresh
319-
func (a *Auth) RefreshHandler() http.HandlerFunc {
328+
func (a *AuthKey) RefreshHandler() http.HandlerFunc {
320329

321330
type response struct {
322331
Status int `json:"status,omitempty"`
@@ -337,7 +346,7 @@ func (a *Auth) RefreshHandler() http.HandlerFunc {
337346
// UserHandler provides the current ApiKey.uMap
338347
//
339348
// .../users
340-
func (a *Auth) UserHandler() http.HandlerFunc {
349+
func (a *AuthKey) UserHandler() http.HandlerFunc {
341350

342351
type user struct {
343352
name, key string
@@ -358,7 +367,7 @@ func (a *Auth) UserHandler() http.HandlerFunc {
358367

359368
w.WriteHeader(http.StatusOK)
360369
fmt.Fprintf(w, "\n%s\n", strings.Repeat("-", 40))
361-
fmt.Fprintf(w, "%-20s | %s\n", "user", "apikey")
370+
fmt.Fprintf(w, "%-20s | %s\n", "user", a.hKey)
362371
fmt.Fprintf(w, "%s\n", strings.Repeat("-", 40))
363372
for i := range users {
364373
if users[i].name != a.admin {
@@ -377,26 +386,26 @@ func (a *Auth) UserHandler() http.HandlerFunc {
377386

378387
// setUser stores the user in the r.Context middleware transport chain
379388
// under the type specific mwUser key
380-
func (a *Auth) setUser(r *http.Request, val string) *http.Request {
389+
func (a *AuthKey) setUser(r *http.Request, val string) *http.Request {
381390
return r.WithContext(context.WithValue(r.Context(), a.mwUser, val))
382391
}
383392

384393
// GetUser retreives the user from the r.Context middleware transport chain
385394
// using the specific mwUser key type
386-
func (a *Auth) GetUser(r *http.Request) string {
395+
func (a *AuthKey) GetUser(r *http.Request) string {
387396
return r.Context().Value(a.mwUser).(string)
388397
}
389398

390399
// IsValid middleware is restriced to valid users and requires
391-
// the http header have token:{apikey} set in the header however
392-
// it will failover and support /api/{akikey}/action formatting
400+
// the http header have [a.hKey:{apikey}] set in the header however
401+
// it will failover and support /api/{key}/action formatting
393402
// within the url string in r.URL.Path
394-
func (a *Auth) IsValid(next http.Handler) http.Handler {
403+
func (a *AuthKey) IsValid(next http.Handler) http.Handler {
395404
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
396405

397-
apikey := r.Header.Get("token")
406+
apikey := r.Header.Get(a.hKey)
398407
if len(apikey) == 0 { // failover to url
399-
apikey = chi.URLParam(r, "apikey")
408+
apikey = chi.URLParam(r, a.hKey)
400409
}
401410

402411
if user, ok := a.check(apikey); ok {
@@ -410,14 +419,14 @@ func (a *Auth) IsValid(next http.Handler) http.Handler {
410419

411420
}
412421

413-
// IsAdmin middleware is restricted to admin and requries
414-
// that a token:{apikey} be set in the request header for access
422+
// IsAdmin middleware is restricted to admin and requries that
423+
// {a.hKey}:{apikey} be set in the request header for access
415424
//
416-
// eg. r.Header [token:{apikey}]
417-
func (a *Auth) IsAdmin(next http.Handler) http.Handler {
425+
// eg. r.Header [a.hKey:{apikey}]
426+
func (a *AuthKey) IsAdmin(next http.Handler) http.Handler {
418427
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
419428

420-
if user, ok := a.check(r.Header.Get("token")); ok && user == a.admin {
429+
if user, ok := a.check(r.Header.Get(a.hKey)); ok && user == a.admin {
421430
next.ServeHTTP(w, a.setUser(r, user))
422431
return
423432
}

auth/passkey.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type PassKey struct {
4343
interval time.Duration // defaults to one-minute
4444
key [20]byte // binary form of secret
4545
tokens [3]atomic.Uint32 // interval tokens
46+
hKey string // header key name; token
4647
}
4748

4849
// NewPassKey configurator used the provided secret or generates a
@@ -55,6 +56,10 @@ func NewPassKey(secret interface{}) *PassKey {
5556
return new(PassKey).Configure(secret)
5657
}
5758

59+
// HKey sets the header key name; {default:token}
60+
func (pk *PassKey) HKey(key string) *PassKey { pk.hKey = key; return pk }
61+
62+
// User will
5863
// Configure applies the provided secret or generates a new one
5964
// and generates a new token set based off the current pk.interval
6065
//
@@ -63,6 +68,11 @@ func NewPassKey(secret interface{}) *PassKey {
6368
// eg. AW6TJVTYMAYJXLWFW2WWJ6D3Q5B2AY25
6469
func (pk *PassKey) Configure(secret interface{}) *PassKey {
6570

71+
// set default hKey
72+
if len(pk.hKey) == 0 {
73+
pk.HKey("token")
74+
}
75+
6676
// apply provided secret or generate a new one
6777
switch a := secret.(type) {
6878
case string:
@@ -191,7 +201,7 @@ func (pk *PassKey) token() {
191201
func (pk *PassKey) IsValid(next http.Handler) http.Handler {
192202
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
193203

194-
passkey := r.Header.Get("token")
204+
passkey := r.Header.Get((pk.hKey))
195205
if len(passkey) > 0 {
196206
tok, _ := strconv.Atoi(passkey)
197207
if pk.Validate(uint32(tok)) {

example/main.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func main() {
4040
fmt.Fprintln(os.Stdout, pk.Secret()) // to stdout
4141
}
4242
private(pk, router)
43-
grace.Manager(pk)
43+
grace.Manager(pk) // pk.Start; roll timer
4444

4545
// demonstration for a client passkey configuration
4646
// for authentication with a passkey remote server
@@ -51,10 +51,11 @@ func main() {
5151
case len(param.AuthKey) > 0:
5252

5353
param.AuthKey = env.Dir(paths.Srv, "conf", param.AuthKey)
54-
ak := auth.NewAuth(&param.AuthKey, router) // .Silent() .User("bob","I'mBobI'mBobI'mBob")
54+
ak := auth.NewAuthKey(&param.AuthKey, router) // .Silent() .User("bob","I'mBobI'mBobI'mBob")
5555
private(ak, router)
5656

5757
default:
58+
5859
log.Println("alert: no auth system configured")
5960
return
6061
}
@@ -66,17 +67,14 @@ func main() {
6667
WriteTimeout: time.Second * 30,
6768
}))
6869

69-
// wait for bootstraps to complete
70-
grace.Done()
71-
72-
// wait for a shutdown signal
73-
grace.Wait()
70+
grace.Done() // wait for bootstraps to complete
71+
grace.Wait() // wait for a shutdown signal
7472

7573
}
7674

7775
// private route; sample auth.IsValid protected routes so that
7876
// is it agnositic to authKey or passKey validation
79-
func private(auth auth.IsValid, router *chi.Mux) {
77+
func private(auth auth.Authentication, router *chi.Mux) {
8078

8179
router.Route("/demo", func(rx chi.Router) {
8280
rx.Use(auth.IsValid)

readme.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ See the ```example``` folder for the following working sample that integrates bo
4242
var param params
4343
paths := env.NewEnv(&param, &srv)
4444
grace := env.NewGraceful()
45-
defer grace.Wait()
4645

4746
// handlers; default public
4847
router := server.Public(server.Heartbeat, nil, nil)
@@ -52,7 +51,7 @@ See the ```example``` folder for the following working sample that integrates bo
5251
case len(param.Secret) > 0:
5352

5453
pk := auth.NewPassKey(param.Secret)
55-
if pk == nil { //
54+
if pk == nil {
5655
pk = auth.NewPassKey(nil)
5756
log.Println("tokens:", pk.Tokens()) // token set
5857
log.Println("passkey:", pk.Secret()) // to stderr
@@ -64,7 +63,7 @@ See the ```example``` folder for the following working sample that integrates bo
6463
case len(param.AuthKey) > 0:
6564

6665
param.AuthKey = env.Dir(paths.Srv, "conf", param.AuthKey)
67-
ak := auth.NewAuth(&param.AuthKey, router) // .Silent() .User("bob","I'mBobI'mBobI'mBob")
66+
ak := auth.NewAuthKey(&param.AuthKey, router) // .Silent() .User("bob","I'mBobI'mBobI'mBob")
6867
private(ak, router)
6968

7069
default:
@@ -79,8 +78,8 @@ See the ```example``` folder for the following working sample that integrates bo
7978
ReadTimeout: time.Second * 10,
8079
WriteTimeout: time.Second * 30,
8180
}))
82-
8381
grace.Done()
82+
grace.Wait()
8483

8584
```
8685

0 commit comments

Comments
 (0)