Skip to content

Commit 1ccc47f

Browse files
authored
Merge pull request #36 from egregors/#27_ifaces-rework
[next]: ifaces rework, divide user.id and user.name, add itself logger close #1 close #31 close #27
2 parents 3ba5c44 + 54ba2f2 commit 1ccc47f

35 files changed

+6388
-729
lines changed

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
run:
2-
go: "1.21"
2+
go: "1.23"
33
timeout: 5m
44
output:
55
format: tab

.mockery.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
with-expecter: True
2+
issue-845-fix: True
3+
resolve-type-alias: False
24
inpackage: True
35
dir: "{{.InterfaceDir}}"
46
mockname: "Mock{{.InterfaceName}}"
@@ -8,6 +10,10 @@ packages:
810
github.com/egregors/passkey:
911
interfaces:
1012
Logger:
13+
User:
1114
UserStore:
1215
SessionStore:
13-
User:
16+
WebAuthnInterface:
17+
github.com/egregors/passkey/deps:
18+
interfaces:
19+
WebAuthnInterface:

README.md

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,32 @@
1515
## Table of Contents
1616

1717
<!-- TOC -->
18-
19-
* [Table of Contents](#table-of-contents)
20-
* [Features](#features)
21-
* [Installation](#installation)
22-
* [Usage](#usage)
18+
* [Table of Contents](#table-of-contents)
19+
* [Features](#features)
20+
* [Installation](#installation)
21+
* [Usage](#usage)
2322
* [Library Usage](#library-usage)
24-
* [Implement the `UserStore` and
25-
`SessionStore` interfaces](#implement-the-userstore-and-sessionstore-interfaces)
26-
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
27-
* [Client-side](#client-side)
23+
* [Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session](#implement-storages-userstore-and-two-sessionstore-one-for-webauthn-and-one-for-general-session)
24+
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
25+
* [Client-side](#client-side)
2826
* [Example Application](#example-application)
29-
* [API](#api)
27+
* [API](#api)
3028
* [Middleware](#middleware)
31-
* [Development](#development)
29+
* [Development](#development)
3230
* [Common tasks](#common-tasks)
3331
* [Mocks](#mocks)
3432
* [Troubleshooting](#troubleshooting)
35-
* [FAQ](#faq)
36-
* [Contributing](#contributing)
37-
* [License](#license)
38-
33+
* [FAQ](#faq)
34+
* [Contributing](#contributing)
35+
* [License](#license)
3936
<!-- TOC -->
4037

4138
## Features
4239

4340
- **User Management**: Handle user information and credentials.
4441
- **WebAuthn Integration**: Easily integrate with WebAuthn for authentication.
4542
- **Session Management**: Manage user sessions securely.
46-
- **Middleware Support**: Implement middleware for authenticated routes.
43+
- **Middleware Support**: Middleware for authenticated routes.
4744

4845
> [!WARNING]
4946
> Stable version is not released yet. The API and the lib are under development.
@@ -74,61 +71,73 @@ go get github.com/egregors/passkey
7471

7572
## Usage
7673

77-
## Terminology
78-
79-
- **WebAuthn Credential ID** – is a unique ID generated by the authenticator (e.g. your smartphone, laptop or hardware
80-
security key like YubiKey) during the registration (sign-up) process of a new credential (passkey).
81-
- **WebAuthn User ID (user.id)** – is an ID specified by the relying party (RP) to represent a user account within their
82-
system. In the context of passkeys, the User ID (user.id) plays a crucial role in linking a particular user with their
83-
credentials (passkeys).
84-
8574
### Library Usage
8675

87-
To add a passkey service to your application, you need to do two things:
76+
To add a passkey service to your application, you need to do a few simple steps:
8877

89-
#### Implement the `UserStore` and `SessionStore` interfaces
78+
#### Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session
9079

9180
```go
9281
package passkey
9382

9483
import "github.com/go-webauthn/webauthn/webauthn"
9584

96-
type User interface {
97-
webauthn.User
98-
PutCredential(webauthn.Credential)
85+
// UserStore is a persistent storage for users and credentials
86+
type UserStore interface {
87+
Create(username string) (User, error)
88+
Update(User) error
89+
90+
Get(userID []byte) (User, error)
91+
GetByName(username string) (User, error)
9992
}
10093

101-
type UserStore interface {
102-
GetOrCreateUser(UserID string) User
103-
SaveUser(User)
94+
// SessionStore is a storage for session data
95+
type SessionStore[T webauthn.SessionData | UserSessionData] interface {
96+
Create(data T) (string, error)
97+
Delete(token string)
98+
99+
Get(token string) (*T, bool)
104100
}
105101

106-
type SessionStore interface {
107-
GenSessionID() (string, error)
108-
GetSession(token string) (*webauthn.SessionData, bool)
109-
SaveSession(token string, data *webauthn.SessionData)
110-
DeleteSession(token string)
102+
```
103+
104+
Your `User` model also should implement `User` interface:
105+
106+
```go
107+
package main
108+
109+
import "github.com/go-webauthn/webauthn/webauthn"
110+
111+
// User is a user with webauthn credentials
112+
type User interface {
113+
webauthn.User
114+
PutCredential(webauthn.Credential)
111115
}
112116

113117
```
114118

119+
This interface is an extension of the `webauthn.User` interface. It adds a `PutCredential` method that allows you to
120+
store a credential in the user object.
121+
115122
#### Create a new `Passkey` instance and mount the routes
116123

124+
The whole example is in `_example` directory.
125+
117126
```go
118127
package main
119128

120129
import (
121130
"embed"
122131
"fmt"
123-
"html/template"
124132
"io/fs"
125133
"net/http"
126134
"net/url"
127-
"os"
128135
"time"
129136

130-
"github.com/egregors/passkey"
131137
"github.com/go-webauthn/webauthn/webauthn"
138+
139+
"github.com/egregors/passkey"
140+
"github.com/egregors/passkey/log"
132141
)
133142

134143
//go:embed web/*
@@ -145,7 +154,7 @@ func main() {
145154

146155
origin := fmt.Sprintf("%s://%s%s%s", proto, sub, host, originPort)
147156

148-
storage := NewStorage()
157+
l := log.NewLogger()
149158

150159
pkey, err := passkey.New(
151160
passkey.Config{
@@ -154,12 +163,13 @@ func main() {
154163
RPID: host, // Generally the FQDN for your site
155164
RPOrigins: []string{origin}, // The origin URLs allowed for WebAuthn
156165
},
157-
UserStore: storage,
158-
SessionStore: storage,
159-
SessionMaxAge: 24 * time.Hour,
166+
UserStore: NewUserStore(),
167+
AuthSessionStore: NewSessionStore[webauthn.SessionData](),
168+
UserSessionStore: NewSessionStore[passkey.UserSessionData](),
160169
},
161-
passkey.WithLogger(NewLogger()),
162-
passkey.WithCookieMaxAge(60*time.Minute),
170+
passkey.WithLogger(l),
171+
passkey.WithUserSessionMaxAge(60*time.Minute),
172+
passkey.WithSessionCookieNamePrefix("passkeyDemo"),
163173
passkey.WithInsecureCookie(), // In order to support Safari on localhost. Do not use in production.
164174
)
165175
if err != nil {
@@ -192,8 +202,8 @@ func main() {
192202
mux.Handle("/private", withAuth(privateMux))
193203

194204
// start the server
195-
fmt.Printf("Listening on %s\n", origin)
196-
if err := http.ListenAndServe(serverPort, mux); err != nil {
205+
l.Infof("Listening on %s\n", origin)
206+
if err := http.ListenAndServe(serverPort, mux); err != nil { //nolint:gosec
197207
panic(err)
198208
}
199209
}
@@ -204,12 +214,12 @@ You can optionally provide a logger to the `New` function using the `WithLogger`
204214

205215
Full list of options:
206216

207-
| Name | Default | Description |
208-
|-----------------------|----------------------------------------|----------------------------------------|
209-
| WithLogger | NullLogger | Provide custom logger |
210-
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
211-
| WithSessionCookieName | `sid` | Sets the name of the session cookie |
212-
| WithCookieMaxAge | 60 minutes | Sets the max age of the session cookie |
217+
| Name | Default | Description |
218+
|-----------------------------|----------------------------------------|------------------------------------------------------|
219+
| WithLogger | NullLogger | Provide custom logger |
220+
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
221+
| WithSessionCookieNamePrefix | `pk` | Sets the name prefix of the session and user cookies |
222+
| WithUserSessionMaxAge | 60 minutes | Sets the max age of the user session cookie |
213223

214224
#### Client-side
215225

@@ -235,11 +245,12 @@ make up
235245

236246
## API
237247

238-
| Method | Description |
239-
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
240-
| `New(cfg Config, opts ...Option) (*Passkey, error)` | Creates a new Passkey instance. |
241-
| `MountRoutes(mux *http.ServeMux, path string)` | Mounts the Passkey routes onto a given HTTP multiplexer. |
242-
| `Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler` | Middleware to protect routes that require authentication. |
248+
| Method | Description |
249+
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
250+
| `New(cfg Config, opts ...Option) (*Passkey, error)` | Creates a new Passkey instance. |
251+
| `MountRoutes(mux *http.ServeMux, path string)` | Mounts the Passkey routes onto a given HTTP multiplexer. |
252+
| `Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler` | Middleware to protect routes that require authentication. |
253+
| `UserIDFromCtx(ctx context.Context, pkUserKey string) ([]byte, bool)` | Returns the user ID from the request context. If the userID is not found, it returns nil and false. |
243254

244255
### Middleware
245256

@@ -251,7 +262,7 @@ Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func (next http.Handl
251262

252263
It takes key for context and two callback functions that are called when the user is authenticated or not.
253264
You can use the context key to retrieve the authenticated userID from the request context
254-
with `passkey.UserFromContext`.
265+
with `passkey.UserIDFromCtx`.
255266

256267
`passkey` contains a helper function:
257268

_example/Dockerfile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
FROM golang:1.22-alpine AS builder
1+
# Description: Dockerfile for building the example application
2+
# It should be call from the root directory of the project because of the context
3+
FROM golang:1.23-alpine AS builder
24

35
WORKDIR /app
46

57
COPY . .
68

7-
RUN go mod init passkey_demo && go mod tidy
8-
9-
RUN go build -o main ./*.go
9+
RUN go build -o ./_example/main ./_example/*.go
1010

1111
FROM scratch
1212

13-
COPY --from=builder /app/main /main
14-
COPY --from=builder /app/web /web
13+
COPY --from=builder /app/_example/main /main
14+
COPY --from=builder /app/_example/web /web
1515

1616
EXPOSE 8080
1717

_example/docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: '3.9'
2-
31
services:
42
traefik:
53
image: traefik:v2.9
@@ -17,14 +15,16 @@ services:
1715
- "443:443"
1816

1917
app:
20-
build: .
18+
build:
19+
context: ../
20+
dockerfile: _example/Dockerfile
2121
image: app
2222
environment:
2323
- PROTO=https
2424
- ORIGIN_PORT=
2525
labels:
2626
- "traefik.enable=true"
2727
- "traefik.http.routers.app.tls=true"
28-
- "traefik.http.routers.app.rule=Host(`localhost`, `192.168.8.151`)"
28+
- "traefik.http.routers.app.rule=Host(`localhost`)"
2929
- "traefik.http.routers.app.entrypoints=websecure"
3030
- "traefik.http.services.app.loadbalancer.server.port=8080"

_example/go.mod

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module example
2+
3+
go 1.23.3
4+
5+
require (
6+
github.com/egregors/passkey v0.0.0
7+
github.com/go-webauthn/webauthn v0.11.2
8+
github.com/google/uuid v1.6.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
14+
github.com/go-webauthn/x v0.1.14 // indirect
15+
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
16+
github.com/google/go-tpm v0.9.1 // indirect
17+
github.com/mitchellh/mapstructure v1.5.0 // indirect
18+
github.com/pmezard/go-difflib v1.0.0 // indirect
19+
github.com/stretchr/objx v0.5.2 // indirect
20+
github.com/stretchr/testify v1.9.0 // indirect
21+
github.com/x448/float16 v0.8.4 // indirect
22+
golang.org/x/crypto v0.26.0 // indirect
23+
golang.org/x/sys v0.23.0 // indirect
24+
gopkg.in/yaml.v3 v3.0.1 // indirect
25+
)
26+
27+
replace github.com/egregors/passkey => ../

_example/go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
4+
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
5+
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
6+
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
7+
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
8+
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
9+
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
10+
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
11+
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
12+
github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
13+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
14+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
15+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
16+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
17+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
20+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
21+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
22+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
23+
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
24+
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
25+
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
26+
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
27+
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
28+
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
30+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)