feat(kvstore): store passkeys and declare the passkey ports
- commit
- dbbae13064a15f4a2fcea5ec8212a6a0230def72
- author
- brice · · 2026-10-12 14:49 UTC
- committer
- brice · · 2026-10-12 14:49 UTC
- parent
- 845634b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +1415 -11
M gen/ermite/storage/session.pb.go +209 -11 generated file: collapsed
A internal/adapters/kvstore/passkeys.go +235 -0
| @@ -0,0 +1,235 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package kvstore | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bytes" | |
| 7 | + | "context" | |
| 8 | + | "crypto/rand" | |
| 9 | + | "encoding/base64" | |
| 10 | + | "errors" | |
| 11 | + | "fmt" | |
| 12 | + | "slices" | |
| 13 | + | "unicode/utf8" | |
| 14 | + | ||
| 15 | + | kv "bnema.dev/ermite-kv" | |
| 16 | + | "github.com/bnema/zerowrap" | |
| 17 | + | "google.golang.org/protobuf/proto" | |
| 18 | + | ||
| 19 | + | storagepb "bnema.dev/ermite/gen/ermite/storage" | |
| 20 | + | "bnema.dev/ermite/internal/domain" | |
| 21 | + | "bnema.dev/ermite/internal/ports" | |
| 22 | + | ) | |
| 23 | + | ||
| 24 | + | const ( | |
| 25 | + | passkeyUserKey = "passkey/user" | |
| 26 | + | passkeyCredPrefix = "passkey/cred/" | |
| 27 | + | ||
| 28 | + | // passkeyHandleLen is the size of the WebAuthn user handle, in bytes. | |
| 29 | + | passkeyHandleLen = 32 | |
| 30 | + | ) | |
| 31 | + | ||
| 32 | + | // Passkeys is the ermite-kv backed ports.PasskeyStore. It shares the database | |
| 33 | + | // of the Store it comes from. | |
| 34 | + | type Passkeys struct { | |
| 35 | + | db *kv.DB | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | var _ ports.PasskeyStore = (*Passkeys)(nil) | |
| 39 | + | ||
| 40 | + | // Passkeys returns the passkey store over the same database. It is valid until | |
| 41 | + | // the Store is closed. | |
| 42 | + | func (s *Store) Passkeys() *Passkeys { return &Passkeys{db: s.db} } | |
| 43 | + | ||
| 44 | + | func passkeyKey(id []byte) []byte { | |
| 45 | + | return []byte(passkeyCredPrefix + base64.RawURLEncoding.EncodeToString(id)) | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | // validPasskeyID reports whether id can be a credential id. | |
| 49 | + | func validPasskeyID(id []byte) bool { return len(id) > 0 && len(id) <= domain.MaxPasskeyIDLen } | |
| 50 | + | ||
| 51 | + | // UserHandle reads passkey/user and generates it when absent. The handle is | |
| 52 | + | // stable for the life of the store. | |
| 53 | + | func (s *Passkeys) UserHandle(ctx context.Context) ([]byte, error) { | |
| 54 | + | for range updateAttempts { | |
| 55 | + | if item, ok := s.db.Get(ctx, []byte(passkeyUserKey)); ok { | |
| 56 | + | var m storagepb.PasskeyUser | |
| 57 | + | if err := proto.Unmarshal(item.Value, &m); err != nil { | |
| 58 | + | return nil, fmt.Errorf("decode passkey user: %w", err) | |
| 59 | + | } | |
| 60 | + | if len(m.GetHandle()) != passkeyHandleLen { | |
| 61 | + | return nil, fmt.Errorf("decode passkey user: %d bytes, want %d", len(m.GetHandle()), passkeyHandleLen) | |
| 62 | + | } | |
| 63 | + | return bytes.Clone(m.GetHandle()), nil | |
| 64 | + | } | |
| 65 | + | handle := make([]byte, passkeyHandleLen) | |
| 66 | + | if _, err := rand.Read(handle); err != nil { | |
| 67 | + | return nil, fmt.Errorf("generate passkey user handle: %w", err) | |
| 68 | + | } | |
| 69 | + | var b kv.Batch | |
| 70 | + | b.RequireAbsent([]byte(passkeyUserKey)) | |
| 71 | + | b.Put([]byte(passkeyUserKey), mustMarshal(&storagepb.PasskeyUser{Handle: handle})) | |
| 72 | + | _, err := s.db.Apply(ctx, &b) | |
| 73 | + | if errors.Is(err, kv.ErrConflict) { | |
| 74 | + | continue // written meanwhile: read it | |
| 75 | + | } | |
| 76 | + | if err != nil { | |
| 77 | + | return nil, fmt.Errorf("store passkey user handle: %w", err) | |
| 78 | + | } | |
| 79 | + | log := zerowrap.FromCtx(ctx) | |
| 80 | + | log.Info().Msg("passkey user handle generated") | |
| 81 | + | return handle, nil | |
| 82 | + | } | |
| 83 | + | return nil, fmt.Errorf("store passkey user handle: %w", domain.ErrConflict) | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // List returns the passkeys ordered by creation time, then id. | |
| 87 | + | func (s *Passkeys) List(ctx context.Context) ([]domain.Passkey, error) { | |
| 88 | + | var out []domain.Passkey | |
| 89 | + | for key, item := range s.db.Snapshot(ctx).Prefix(ctx, []byte(passkeyCredPrefix)) { | |
| 90 | + | p, err := decodePasskey(item.Value) | |
| 91 | + | if err != nil { | |
| 92 | + | return nil, fmt.Errorf("decode %s: %w", key, err) | |
| 93 | + | } | |
| 94 | + | out = append(out, p) | |
| 95 | + | } | |
| 96 | + | slices.SortStableFunc(out, func(a, b domain.Passkey) int { | |
| 97 | + | if c := a.CreatedAt.Compare(b.CreatedAt); c != 0 { | |
| 98 | + | return c | |
| 99 | + | } | |
| 100 | + | return bytes.Compare(a.ID, b.ID) | |
| 101 | + | }) | |
| 102 | + | return out, nil | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | // Get reads passkey/cred/<id>. | |
| 106 | + | func (s *Passkeys) Get(ctx context.Context, id []byte) (domain.Passkey, error) { | |
| 107 | + | if !validPasskeyID(id) { | |
| 108 | + | return domain.Passkey{}, domain.ErrNotFound | |
| 109 | + | } | |
| 110 | + | item, ok := s.db.Get(ctx, passkeyKey(id)) | |
| 111 | + | if !ok { | |
| 112 | + | return domain.Passkey{}, domain.ErrNotFound | |
| 113 | + | } | |
| 114 | + | return decodePasskey(item.Value) | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // Create stores passkey/cred/<id> with an "absent" precondition: a taken id | |
| 118 | + | // yields domain.ErrAlreadyExists. The caller bounds the number of passkeys. | |
| 119 | + | func (s *Passkeys) Create(ctx context.Context, p domain.Passkey) error { | |
| 120 | + | val, err := encodePasskey(p) | |
| 121 | + | if err != nil { | |
| 122 | + | return err | |
| 123 | + | } | |
| 124 | + | var b kv.Batch | |
| 125 | + | b.RequireAbsent(passkeyKey(p.ID)) | |
| 126 | + | b.Put(passkeyKey(p.ID), val) | |
| 127 | + | if _, err := s.db.Apply(ctx, &b); err != nil { | |
| 128 | + | if errors.Is(err, kv.ErrConflict) { | |
| 129 | + | return domain.ErrAlreadyExists | |
| 130 | + | } | |
| 131 | + | return fmt.Errorf("create passkey: %w", err) | |
| 132 | + | } | |
| 133 | + | return nil | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | // Update replaces a stored passkey, or returns domain.ErrNotFound. | |
| 137 | + | // | |
| 138 | + | //hexcheck:ignore suspicious-business-logic-in-adapter version-checked replace, not a policy | |
| 139 | + | func (s *Passkeys) Update(ctx context.Context, p domain.Passkey) error { | |
| 140 | + | val, err := encodePasskey(p) | |
| 141 | + | if err != nil { | |
| 142 | + | return err | |
| 143 | + | } | |
| 144 | + | for range updateAttempts { | |
| 145 | + | item, ok := s.db.Get(ctx, passkeyKey(p.ID)) | |
| 146 | + | if !ok { | |
| 147 | + | return domain.ErrNotFound | |
| 148 | + | } | |
| 149 | + | var b kv.Batch | |
| 150 | + | b.RequireVersion(passkeyKey(p.ID), item.Version) | |
| 151 | + | b.Put(passkeyKey(p.ID), val) | |
| 152 | + | _, err := s.db.Apply(ctx, &b) | |
| 153 | + | if errors.Is(err, kv.ErrConflict) { | |
| 154 | + | continue // changed or removed meanwhile: read again | |
| 155 | + | } | |
| 156 | + | if err != nil { | |
| 157 | + | return fmt.Errorf("update passkey: %w", err) | |
| 158 | + | } | |
| 159 | + | return nil | |
| 160 | + | } | |
| 161 | + | return fmt.Errorf("update passkey: %w", domain.ErrConflict) | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // Delete removes passkey/cred/<id>, or returns domain.ErrNotFound. | |
| 165 | + | func (s *Passkeys) Delete(ctx context.Context, id []byte) error { | |
| 166 | + | if !validPasskeyID(id) { | |
| 167 | + | return domain.ErrNotFound | |
| 168 | + | } | |
| 169 | + | for range updateAttempts { | |
| 170 | + | item, ok := s.db.Get(ctx, passkeyKey(id)) | |
| 171 | + | if !ok { | |
| 172 | + | return domain.ErrNotFound | |
| 173 | + | } | |
| 174 | + | var b kv.Batch | |
| 175 | + | b.RequireVersion(passkeyKey(id), item.Version) | |
| 176 | + | b.Delete(passkeyKey(id)) | |
| 177 | + | _, err := s.db.Apply(ctx, &b) | |
| 178 | + | if errors.Is(err, kv.ErrConflict) { | |
| 179 | + | continue | |
| 180 | + | } | |
| 181 | + | if err != nil { | |
| 182 | + | return fmt.Errorf("delete passkey: %w", err) | |
| 183 | + | } | |
| 184 | + | return nil | |
| 185 | + | } | |
| 186 | + | return fmt.Errorf("delete passkey: %w", domain.ErrConflict) | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | func encodePasskey(p domain.Passkey) ([]byte, error) { | |
| 190 | + | if !validPasskeyID(p.ID) { | |
| 191 | + | return nil, fmt.Errorf("%w: passkey id has %d bytes", domain.ErrInvalid, len(p.ID)) | |
| 192 | + | } | |
| 193 | + | if !utf8.ValidString(p.Name) || !utf8.ValidString(p.AttestationType) { | |
| 194 | + | return nil, fmt.Errorf("%w: passkey text is not valid UTF-8", domain.ErrInvalid) | |
| 195 | + | } | |
| 196 | + | for _, t := range p.Transports { | |
| 197 | + | if !utf8.ValidString(t) { | |
| 198 | + | return nil, fmt.Errorf("%w: passkey transport is not valid UTF-8", domain.ErrInvalid) | |
| 199 | + | } | |
| 200 | + | } | |
| 201 | + | return mustMarshal(&storagepb.Passkey{ | |
| 202 | + | Id: p.ID, | |
| 203 | + | Name: p.Name, | |
| 204 | + | PublicKey: p.PublicKey, | |
| 205 | + | AttestationType: p.AttestationType, | |
| 206 | + | Transports: p.Transports, | |
| 207 | + | SignCount: p.SignCount, | |
| 208 | + | BackupEligible: p.BackupEligible, | |
| 209 | + | BackupState: p.BackupState, | |
| 210 | + | CreatedAt: encodeTime(p.CreatedAt), | |
| 211 | + | LastUsedAt: encodeTime(p.LastUsedAt), | |
| 212 | + | }), nil | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | func decodePasskey(val []byte) (domain.Passkey, error) { | |
| 216 | + | var pb storagepb.Passkey | |
| 217 | + | if err := proto.Unmarshal(val, &pb); err != nil { | |
| 218 | + | return domain.Passkey{}, fmt.Errorf("decode passkey: %w", err) | |
| 219 | + | } | |
| 220 | + | if !validPasskeyID(pb.GetId()) { | |
| 221 | + | return domain.Passkey{}, fmt.Errorf("decode passkey: id has %d bytes", len(pb.GetId())) | |
| 222 | + | } | |
| 223 | + | return domain.Passkey{ | |
| 224 | + | ID: bytes.Clone(pb.GetId()), | |
| 225 | + | Name: pb.GetName(), | |
| 226 | + | PublicKey: bytes.Clone(pb.GetPublicKey()), | |
| 227 | + | AttestationType: pb.GetAttestationType(), | |
| 228 | + | Transports: slices.Clone(pb.GetTransports()), | |
| 229 | + | SignCount: pb.GetSignCount(), | |
| 230 | + | BackupEligible: pb.GetBackupEligible(), | |
| 231 | + | BackupState: pb.GetBackupState(), | |
| 232 | + | CreatedAt: decodeTime(pb.GetCreatedAt()), | |
| 233 | + | LastUsedAt: decodeTime(pb.GetLastUsedAt()), | |
| 234 | + | }, nil | |
| 235 | + | } | |
| @@ -0,0 +1,235 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package kvstore | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "bytes" | |||
| 7 | + | "context" | |||
| 8 | + | "crypto/rand" | |||
| 9 | + | "encoding/base64" | |||
| 10 | + | "errors" | |||
| 11 | + | "fmt" | |||
| 12 | + | "slices" | |||
| 13 | + | "unicode/utf8" | |||
| 14 | + | ||||
| 15 | + | kv "bnema.dev/ermite-kv" | |||
| 16 | + | "github.com/bnema/zerowrap" | |||
| 17 | + | "google.golang.org/protobuf/proto" | |||
| 18 | + | ||||
| 19 | + | storagepb "bnema.dev/ermite/gen/ermite/storage" | |||
| 20 | + | "bnema.dev/ermite/internal/domain" | |||
| 21 | + | "bnema.dev/ermite/internal/ports" | |||
| 22 | + | ) | |||
| 23 | + | ||||
| 24 | + | const ( | |||
| 25 | + | passkeyUserKey = "passkey/user" | |||
| 26 | + | passkeyCredPrefix = "passkey/cred/" | |||
| 27 | + | ||||
| 28 | + | // passkeyHandleLen is the size of the WebAuthn user handle, in bytes. | |||
| 29 | + | passkeyHandleLen = 32 | |||
| 30 | + | ) | |||
| 31 | + | ||||
| 32 | + | // Passkeys is the ermite-kv backed ports.PasskeyStore. It shares the database | |||
| 33 | + | // of the Store it comes from. | |||
| 34 | + | type Passkeys struct { | |||
| 35 | + | db *kv.DB | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | var _ ports.PasskeyStore = (*Passkeys)(nil) | |||
| 39 | + | ||||
| 40 | + | // Passkeys returns the passkey store over the same database. It is valid until | |||
| 41 | + | // the Store is closed. | |||
| 42 | + | func (s *Store) Passkeys() *Passkeys { return &Passkeys{db: s.db} } | |||
| 43 | + | ||||
| 44 | + | func passkeyKey(id []byte) []byte { | |||
| 45 | + | return []byte(passkeyCredPrefix + base64.RawURLEncoding.EncodeToString(id)) | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | // validPasskeyID reports whether id can be a credential id. | |||
| 49 | + | func validPasskeyID(id []byte) bool { return len(id) > 0 && len(id) <= domain.MaxPasskeyIDLen } | |||
| 50 | + | ||||
| 51 | + | // UserHandle reads passkey/user and generates it when absent. The handle is | |||
| 52 | + | // stable for the life of the store. | |||
| 53 | + | func (s *Passkeys) UserHandle(ctx context.Context) ([]byte, error) { | |||
| 54 | + | for range updateAttempts { | |||
| 55 | + | if item, ok := s.db.Get(ctx, []byte(passkeyUserKey)); ok { | |||
| 56 | + | var m storagepb.PasskeyUser | |||
| 57 | + | if err := proto.Unmarshal(item.Value, &m); err != nil { | |||
| 58 | + | return nil, fmt.Errorf("decode passkey user: %w", err) | |||
| 59 | + | } | |||
| 60 | + | if len(m.GetHandle()) != passkeyHandleLen { | |||
| 61 | + | return nil, fmt.Errorf("decode passkey user: %d bytes, want %d", len(m.GetHandle()), passkeyHandleLen) | |||
| 62 | + | } | |||
| 63 | + | return bytes.Clone(m.GetHandle()), nil | |||
| 64 | + | } | |||
| 65 | + | handle := make([]byte, passkeyHandleLen) | |||
| 66 | + | if _, err := rand.Read(handle); err != nil { | |||
| 67 | + | return nil, fmt.Errorf("generate passkey user handle: %w", err) | |||
| 68 | + | } | |||
| 69 | + | var b kv.Batch | |||
| 70 | + | b.RequireAbsent([]byte(passkeyUserKey)) | |||
| 71 | + | b.Put([]byte(passkeyUserKey), mustMarshal(&storagepb.PasskeyUser{Handle: handle})) | |||
| 72 | + | _, err := s.db.Apply(ctx, &b) | |||
| 73 | + | if errors.Is(err, kv.ErrConflict) { | |||
| 74 | + | continue // written meanwhile: read it | |||
| 75 | + | } | |||
| 76 | + | if err != nil { | |||
| 77 | + | return nil, fmt.Errorf("store passkey user handle: %w", err) | |||
| 78 | + | } | |||
| 79 | + | log := zerowrap.FromCtx(ctx) | |||
| 80 | + | log.Info().Msg("passkey user handle generated") | |||
| 81 | + | return handle, nil | |||
| 82 | + | } | |||
| 83 | + | return nil, fmt.Errorf("store passkey user handle: %w", domain.ErrConflict) | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // List returns the passkeys ordered by creation time, then id. | |||
| 87 | + | func (s *Passkeys) List(ctx context.Context) ([]domain.Passkey, error) { | |||
| 88 | + | var out []domain.Passkey | |||
| 89 | + | for key, item := range s.db.Snapshot(ctx).Prefix(ctx, []byte(passkeyCredPrefix)) { | |||
| 90 | + | p, err := decodePasskey(item.Value) | |||
| 91 | + | if err != nil { | |||
| 92 | + | return nil, fmt.Errorf("decode %s: %w", key, err) | |||
| 93 | + | } | |||
| 94 | + | out = append(out, p) | |||
| 95 | + | } | |||
| 96 | + | slices.SortStableFunc(out, func(a, b domain.Passkey) int { | |||
| 97 | + | if c := a.CreatedAt.Compare(b.CreatedAt); c != 0 { | |||
| 98 | + | return c | |||
| 99 | + | } | |||
| 100 | + | return bytes.Compare(a.ID, b.ID) | |||
| 101 | + | }) | |||
| 102 | + | return out, nil | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | // Get reads passkey/cred/<id>. | |||
| 106 | + | func (s *Passkeys) Get(ctx context.Context, id []byte) (domain.Passkey, error) { | |||
| 107 | + | if !validPasskeyID(id) { | |||
| 108 | + | return domain.Passkey{}, domain.ErrNotFound | |||
| 109 | + | } | |||
| 110 | + | item, ok := s.db.Get(ctx, passkeyKey(id)) | |||
| 111 | + | if !ok { | |||
| 112 | + | return domain.Passkey{}, domain.ErrNotFound | |||
| 113 | + | } | |||
| 114 | + | return decodePasskey(item.Value) | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // Create stores passkey/cred/<id> with an "absent" precondition: a taken id | |||
| 118 | + | // yields domain.ErrAlreadyExists. The caller bounds the number of passkeys. | |||
| 119 | + | func (s *Passkeys) Create(ctx context.Context, p domain.Passkey) error { | |||
| 120 | + | val, err := encodePasskey(p) | |||
| 121 | + | if err != nil { | |||
| 122 | + | return err | |||
| 123 | + | } | |||
| 124 | + | var b kv.Batch | |||
| 125 | + | b.RequireAbsent(passkeyKey(p.ID)) | |||
| 126 | + | b.Put(passkeyKey(p.ID), val) | |||
| 127 | + | if _, err := s.db.Apply(ctx, &b); err != nil { | |||
| 128 | + | if errors.Is(err, kv.ErrConflict) { | |||
| 129 | + | return domain.ErrAlreadyExists | |||
| 130 | + | } | |||
| 131 | + | return fmt.Errorf("create passkey: %w", err) | |||
| 132 | + | } | |||
| 133 | + | return nil | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | // Update replaces a stored passkey, or returns domain.ErrNotFound. | |||
| 137 | + | // | |||
| 138 | + | //hexcheck:ignore suspicious-business-logic-in-adapter version-checked replace, not a policy | |||
| 139 | + | func (s *Passkeys) Update(ctx context.Context, p domain.Passkey) error { | |||
| 140 | + | val, err := encodePasskey(p) | |||
| 141 | + | if err != nil { | |||
| 142 | + | return err | |||
| 143 | + | } | |||
| 144 | + | for range updateAttempts { | |||
| 145 | + | item, ok := s.db.Get(ctx, passkeyKey(p.ID)) | |||
| 146 | + | if !ok { | |||
| 147 | + | return domain.ErrNotFound | |||
| 148 | + | } | |||
| 149 | + | var b kv.Batch | |||
| 150 | + | b.RequireVersion(passkeyKey(p.ID), item.Version) | |||
| 151 | + | b.Put(passkeyKey(p.ID), val) | |||
| 152 | + | _, err := s.db.Apply(ctx, &b) | |||
| 153 | + | if errors.Is(err, kv.ErrConflict) { | |||
| 154 | + | continue // changed or removed meanwhile: read again | |||
| 155 | + | } | |||
| 156 | + | if err != nil { | |||
| 157 | + | return fmt.Errorf("update passkey: %w", err) | |||
| 158 | + | } | |||
| 159 | + | return nil | |||
| 160 | + | } | |||
| 161 | + | return fmt.Errorf("update passkey: %w", domain.ErrConflict) | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // Delete removes passkey/cred/<id>, or returns domain.ErrNotFound. | |||
| 165 | + | func (s *Passkeys) Delete(ctx context.Context, id []byte) error { | |||
| 166 | + | if !validPasskeyID(id) { | |||
| 167 | + | return domain.ErrNotFound | |||
| 168 | + | } | |||
| 169 | + | for range updateAttempts { | |||
| 170 | + | item, ok := s.db.Get(ctx, passkeyKey(id)) | |||
| 171 | + | if !ok { | |||
| 172 | + | return domain.ErrNotFound | |||
| 173 | + | } | |||
| 174 | + | var b kv.Batch | |||
| 175 | + | b.RequireVersion(passkeyKey(id), item.Version) | |||
| 176 | + | b.Delete(passkeyKey(id)) | |||
| 177 | + | _, err := s.db.Apply(ctx, &b) | |||
| 178 | + | if errors.Is(err, kv.ErrConflict) { | |||
| 179 | + | continue | |||
| 180 | + | } | |||
| 181 | + | if err != nil { | |||
| 182 | + | return fmt.Errorf("delete passkey: %w", err) | |||
| 183 | + | } | |||
| 184 | + | return nil | |||
| 185 | + | } | |||
| 186 | + | return fmt.Errorf("delete passkey: %w", domain.ErrConflict) | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | func encodePasskey(p domain.Passkey) ([]byte, error) { | |||
| 190 | + | if !validPasskeyID(p.ID) { | |||
| 191 | + | return nil, fmt.Errorf("%w: passkey id has %d bytes", domain.ErrInvalid, len(p.ID)) | |||
| 192 | + | } | |||
| 193 | + | if !utf8.ValidString(p.Name) || !utf8.ValidString(p.AttestationType) { | |||
| 194 | + | return nil, fmt.Errorf("%w: passkey text is not valid UTF-8", domain.ErrInvalid) | |||
| 195 | + | } | |||
| 196 | + | for _, t := range p.Transports { | |||
| 197 | + | if !utf8.ValidString(t) { | |||
| 198 | + | return nil, fmt.Errorf("%w: passkey transport is not valid UTF-8", domain.ErrInvalid) | |||
| 199 | + | } | |||
| 200 | + | } | |||
| 201 | + | return mustMarshal(&storagepb.Passkey{ | |||
| 202 | + | Id: p.ID, | |||
| 203 | + | Name: p.Name, | |||
| 204 | + | PublicKey: p.PublicKey, | |||
| 205 | + | AttestationType: p.AttestationType, | |||
| 206 | + | Transports: p.Transports, | |||
| 207 | + | SignCount: p.SignCount, | |||
| 208 | + | BackupEligible: p.BackupEligible, | |||
| 209 | + | BackupState: p.BackupState, | |||
| 210 | + | CreatedAt: encodeTime(p.CreatedAt), | |||
| 211 | + | LastUsedAt: encodeTime(p.LastUsedAt), | |||
| 212 | + | }), nil | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | func decodePasskey(val []byte) (domain.Passkey, error) { | |||
| 216 | + | var pb storagepb.Passkey | |||
| 217 | + | if err := proto.Unmarshal(val, &pb); err != nil { | |||
| 218 | + | return domain.Passkey{}, fmt.Errorf("decode passkey: %w", err) | |||
| 219 | + | } | |||
| 220 | + | if !validPasskeyID(pb.GetId()) { | |||
| 221 | + | return domain.Passkey{}, fmt.Errorf("decode passkey: id has %d bytes", len(pb.GetId())) | |||
| 222 | + | } | |||
| 223 | + | return domain.Passkey{ | |||
| 224 | + | ID: bytes.Clone(pb.GetId()), | |||
| 225 | + | Name: pb.GetName(), | |||
| 226 | + | PublicKey: bytes.Clone(pb.GetPublicKey()), | |||
| 227 | + | AttestationType: pb.GetAttestationType(), | |||
| 228 | + | Transports: slices.Clone(pb.GetTransports()), | |||
| 229 | + | SignCount: pb.GetSignCount(), | |||
| 230 | + | BackupEligible: pb.GetBackupEligible(), | |||
| 231 | + | BackupState: pb.GetBackupState(), | |||
| 232 | + | CreatedAt: decodeTime(pb.GetCreatedAt()), | |||
| 233 | + | LastUsedAt: decodeTime(pb.GetLastUsedAt()), | |||
| 234 | + | }, nil | |||
| 235 | + | } | |||
A internal/adapters/kvstore/passkeys_test.go +151 -0
| @@ -0,0 +1,151 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package kvstore_test | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bytes" | |
| 7 | + | "context" | |
| 8 | + | "sync" | |
| 9 | + | "testing" | |
| 10 | + | "time" | |
| 11 | + | ||
| 12 | + | "github.com/stretchr/testify/assert" | |
| 13 | + | "github.com/stretchr/testify/require" | |
| 14 | + | ||
| 15 | + | "bnema.dev/ermite/internal/domain" | |
| 16 | + | ) | |
| 17 | + | ||
| 18 | + | func newPasskey(id string, at time.Time) domain.Passkey { | |
| 19 | + | return domain.Passkey{ | |
| 20 | + | ID: []byte(id), | |
| 21 | + | Name: "key " + id, | |
| 22 | + | PublicKey: []byte("cose-" + id), | |
| 23 | + | AttestationType: "none", | |
| 24 | + | Transports: []string{"internal", "hybrid"}, | |
| 25 | + | SignCount: 7, | |
| 26 | + | BackupEligible: true, | |
| 27 | + | BackupState: true, | |
| 28 | + | CreatedAt: at, | |
| 29 | + | LastUsedAt: at.Add(time.Hour), | |
| 30 | + | } | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | func TestPasskeysRoundTrip(t *testing.T) { | |
| 34 | + | ctx := context.Background() | |
| 35 | + | ps := openStore(t, t.TempDir()).Passkeys() | |
| 36 | + | ||
| 37 | + | p := newPasskey("cred-1", created) | |
| 38 | + | require.NoError(t, ps.Create(ctx, p)) | |
| 39 | + | got, err := ps.Get(ctx, p.ID) | |
| 40 | + | require.NoError(t, err) | |
| 41 | + | assert.Equal(t, p, got) | |
| 42 | + | ||
| 43 | + | // Minimal passkey: no transports, never used. A binary id works too. | |
| 44 | + | min := domain.Passkey{ID: []byte{0, 1, 0xff, '/'}, Name: "min", PublicKey: []byte("k"), CreatedAt: created} | |
| 45 | + | require.NoError(t, ps.Create(ctx, min)) | |
| 46 | + | got, err = ps.Get(ctx, min.ID) | |
| 47 | + | require.NoError(t, err) | |
| 48 | + | assert.Empty(t, got.Transports) | |
| 49 | + | assert.True(t, got.LastUsedAt.IsZero()) | |
| 50 | + | assert.Equal(t, min.ID, got.ID) | |
| 51 | + | ||
| 52 | + | long := domain.Passkey{ID: bytes.Repeat([]byte{7}, domain.MaxPasskeyIDLen), Name: "long", PublicKey: []byte("k"), CreatedAt: created} | |
| 53 | + | require.NoError(t, ps.Create(ctx, long)) | |
| 54 | + | tooLong := domain.Passkey{ID: bytes.Repeat([]byte{7}, domain.MaxPasskeyIDLen+1), Name: "x", PublicKey: []byte("k")} | |
| 55 | + | assert.ErrorIs(t, ps.Create(ctx, tooLong), domain.ErrInvalid) | |
| 56 | + | assert.ErrorIs(t, ps.Create(ctx, domain.Passkey{Name: "no id"}), domain.ErrInvalid) | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | func TestPasskeysDuplicateAndUnknown(t *testing.T) { | |
| 60 | + | ctx := context.Background() | |
| 61 | + | ps := openStore(t, t.TempDir()).Passkeys() | |
| 62 | + | p := newPasskey("cred-1", created) | |
| 63 | + | require.NoError(t, ps.Create(ctx, p)) | |
| 64 | + | ||
| 65 | + | assert.ErrorIs(t, ps.Create(ctx, p), domain.ErrAlreadyExists) | |
| 66 | + | ||
| 67 | + | unknown := newPasskey("nope", created) | |
| 68 | + | _, err := ps.Get(ctx, unknown.ID) | |
| 69 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 70 | + | assert.ErrorIs(t, ps.Update(ctx, unknown), domain.ErrNotFound) | |
| 71 | + | assert.ErrorIs(t, ps.Delete(ctx, unknown.ID), domain.ErrNotFound) | |
| 72 | + | _, err = ps.Get(ctx, nil) | |
| 73 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 74 | + | assert.ErrorIs(t, ps.Delete(ctx, nil), domain.ErrNotFound) | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | func TestPasskeysUpdateDelete(t *testing.T) { | |
| 78 | + | ctx := context.Background() | |
| 79 | + | ps := openStore(t, t.TempDir()).Passkeys() | |
| 80 | + | p := newPasskey("cred-1", created) | |
| 81 | + | require.NoError(t, ps.Create(ctx, p)) | |
| 82 | + | ||
| 83 | + | p.SignCount = 42 | |
| 84 | + | p.BackupState = false | |
| 85 | + | p.LastUsedAt = created.Add(48 * time.Hour) | |
| 86 | + | require.NoError(t, ps.Update(ctx, p)) | |
| 87 | + | got, err := ps.Get(ctx, p.ID) | |
| 88 | + | require.NoError(t, err) | |
| 89 | + | assert.Equal(t, p, got) | |
| 90 | + | ||
| 91 | + | require.NoError(t, ps.Delete(ctx, p.ID)) | |
| 92 | + | _, err = ps.Get(ctx, p.ID) | |
| 93 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 94 | + | assert.ErrorIs(t, ps.Delete(ctx, p.ID), domain.ErrNotFound) | |
| 95 | + | list, err := ps.List(ctx) | |
| 96 | + | require.NoError(t, err) | |
| 97 | + | assert.Empty(t, list) | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | // List orders by creation time, then by id. | |
| 101 | + | func TestPasskeysListOrder(t *testing.T) { | |
| 102 | + | ctx := context.Background() | |
| 103 | + | ps := openStore(t, t.TempDir()).Passkeys() | |
| 104 | + | for _, p := range []domain.Passkey{ | |
| 105 | + | newPasskey("c", created.Add(2*time.Hour)), | |
| 106 | + | newPasskey("b", created), | |
| 107 | + | newPasskey("a", created), | |
| 108 | + | newPasskey("d", created.Add(time.Hour)), | |
| 109 | + | } { | |
| 110 | + | require.NoError(t, ps.Create(ctx, p)) | |
| 111 | + | } | |
| 112 | + | list, err := ps.List(ctx) | |
| 113 | + | require.NoError(t, err) | |
| 114 | + | var ids []string | |
| 115 | + | for _, p := range list { | |
| 116 | + | ids = append(ids, string(p.ID)) | |
| 117 | + | } | |
| 118 | + | assert.Equal(t, []string{"a", "b", "d", "c"}, ids) | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | // The handle is generated once, stays stable across a restart, and concurrent | |
| 122 | + | // first calls agree on it. | |
| 123 | + | func TestPasskeysUserHandle(t *testing.T) { | |
| 124 | + | ctx := context.Background() | |
| 125 | + | dir := t.TempDir() | |
| 126 | + | s := openStore(t, dir) | |
| 127 | + | ||
| 128 | + | var wg sync.WaitGroup | |
| 129 | + | handles := make([][]byte, 8) | |
| 130 | + | for i := range handles { | |
| 131 | + | wg.Add(1) | |
| 132 | + | go func() { | |
| 133 | + | defer wg.Done() | |
| 134 | + | h, err := s.Passkeys().UserHandle(ctx) | |
| 135 | + | assert.NoError(t, err) | |
| 136 | + | handles[i] = h | |
| 137 | + | }() | |
| 138 | + | } | |
| 139 | + | wg.Wait() | |
| 140 | + | handle := handles[0] | |
| 141 | + | assert.Len(t, handle, 32) | |
| 142 | + | for _, h := range handles[1:] { | |
| 143 | + | assert.Equal(t, handle, h) | |
| 144 | + | } | |
| 145 | + | require.NoError(t, s.Close(ctx)) | |
| 146 | + | ||
| 147 | + | s = openStore(t, dir) | |
| 148 | + | again, err := s.Passkeys().UserHandle(ctx) | |
| 149 | + | require.NoError(t, err) | |
| 150 | + | assert.Equal(t, handle, again, "a restart keeps the handle") | |
| 151 | + | } | |
| @@ -0,0 +1,151 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package kvstore_test | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "bytes" | |||
| 7 | + | "context" | |||
| 8 | + | "sync" | |||
| 9 | + | "testing" | |||
| 10 | + | "time" | |||
| 11 | + | ||||
| 12 | + | "github.com/stretchr/testify/assert" | |||
| 13 | + | "github.com/stretchr/testify/require" | |||
| 14 | + | ||||
| 15 | + | "bnema.dev/ermite/internal/domain" | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 18 | + | func newPasskey(id string, at time.Time) domain.Passkey { | |||
| 19 | + | return domain.Passkey{ | |||
| 20 | + | ID: []byte(id), | |||
| 21 | + | Name: "key " + id, | |||
| 22 | + | PublicKey: []byte("cose-" + id), | |||
| 23 | + | AttestationType: "none", | |||
| 24 | + | Transports: []string{"internal", "hybrid"}, | |||
| 25 | + | SignCount: 7, | |||
| 26 | + | BackupEligible: true, | |||
| 27 | + | BackupState: true, | |||
| 28 | + | CreatedAt: at, | |||
| 29 | + | LastUsedAt: at.Add(time.Hour), | |||
| 30 | + | } | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | func TestPasskeysRoundTrip(t *testing.T) { | |||
| 34 | + | ctx := context.Background() | |||
| 35 | + | ps := openStore(t, t.TempDir()).Passkeys() | |||
| 36 | + | ||||
| 37 | + | p := newPasskey("cred-1", created) | |||
| 38 | + | require.NoError(t, ps.Create(ctx, p)) | |||
| 39 | + | got, err := ps.Get(ctx, p.ID) | |||
| 40 | + | require.NoError(t, err) | |||
| 41 | + | assert.Equal(t, p, got) | |||
| 42 | + | ||||
| 43 | + | // Minimal passkey: no transports, never used. A binary id works too. | |||
| 44 | + | min := domain.Passkey{ID: []byte{0, 1, 0xff, '/'}, Name: "min", PublicKey: []byte("k"), CreatedAt: created} | |||
| 45 | + | require.NoError(t, ps.Create(ctx, min)) | |||
| 46 | + | got, err = ps.Get(ctx, min.ID) | |||
| 47 | + | require.NoError(t, err) | |||
| 48 | + | assert.Empty(t, got.Transports) | |||
| 49 | + | assert.True(t, got.LastUsedAt.IsZero()) | |||
| 50 | + | assert.Equal(t, min.ID, got.ID) | |||
| 51 | + | ||||
| 52 | + | long := domain.Passkey{ID: bytes.Repeat([]byte{7}, domain.MaxPasskeyIDLen), Name: "long", PublicKey: []byte("k"), CreatedAt: created} | |||
| 53 | + | require.NoError(t, ps.Create(ctx, long)) | |||
| 54 | + | tooLong := domain.Passkey{ID: bytes.Repeat([]byte{7}, domain.MaxPasskeyIDLen+1), Name: "x", PublicKey: []byte("k")} | |||
| 55 | + | assert.ErrorIs(t, ps.Create(ctx, tooLong), domain.ErrInvalid) | |||
| 56 | + | assert.ErrorIs(t, ps.Create(ctx, domain.Passkey{Name: "no id"}), domain.ErrInvalid) | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | func TestPasskeysDuplicateAndUnknown(t *testing.T) { | |||
| 60 | + | ctx := context.Background() | |||
| 61 | + | ps := openStore(t, t.TempDir()).Passkeys() | |||
| 62 | + | p := newPasskey("cred-1", created) | |||
| 63 | + | require.NoError(t, ps.Create(ctx, p)) | |||
| 64 | + | ||||
| 65 | + | assert.ErrorIs(t, ps.Create(ctx, p), domain.ErrAlreadyExists) | |||
| 66 | + | ||||
| 67 | + | unknown := newPasskey("nope", created) | |||
| 68 | + | _, err := ps.Get(ctx, unknown.ID) | |||
| 69 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |||
| 70 | + | assert.ErrorIs(t, ps.Update(ctx, unknown), domain.ErrNotFound) | |||
| 71 | + | assert.ErrorIs(t, ps.Delete(ctx, unknown.ID), domain.ErrNotFound) | |||
| 72 | + | _, err = ps.Get(ctx, nil) | |||
| 73 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |||
| 74 | + | assert.ErrorIs(t, ps.Delete(ctx, nil), domain.ErrNotFound) | |||
| 75 | + | } | |||
| 76 | + | ||||
| 77 | + | func TestPasskeysUpdateDelete(t *testing.T) { | |||
| 78 | + | ctx := context.Background() | |||
| 79 | + | ps := openStore(t, t.TempDir()).Passkeys() | |||
| 80 | + | p := newPasskey("cred-1", created) | |||
| 81 | + | require.NoError(t, ps.Create(ctx, p)) | |||
| 82 | + | ||||
| 83 | + | p.SignCount = 42 | |||
| 84 | + | p.BackupState = false | |||
| 85 | + | p.LastUsedAt = created.Add(48 * time.Hour) | |||
| 86 | + | require.NoError(t, ps.Update(ctx, p)) | |||
| 87 | + | got, err := ps.Get(ctx, p.ID) | |||
| 88 | + | require.NoError(t, err) | |||
| 89 | + | assert.Equal(t, p, got) | |||
| 90 | + | ||||
| 91 | + | require.NoError(t, ps.Delete(ctx, p.ID)) | |||
| 92 | + | _, err = ps.Get(ctx, p.ID) | |||
| 93 | + | assert.ErrorIs(t, err, domain.ErrNotFound) | |||
| 94 | + | assert.ErrorIs(t, ps.Delete(ctx, p.ID), domain.ErrNotFound) | |||
| 95 | + | list, err := ps.List(ctx) | |||
| 96 | + | require.NoError(t, err) | |||
| 97 | + | assert.Empty(t, list) | |||
| 98 | + | } | |||
| 99 | + | ||||
| 100 | + | // List orders by creation time, then by id. | |||
| 101 | + | func TestPasskeysListOrder(t *testing.T) { | |||
| 102 | + | ctx := context.Background() | |||
| 103 | + | ps := openStore(t, t.TempDir()).Passkeys() | |||
| 104 | + | for _, p := range []domain.Passkey{ | |||
| 105 | + | newPasskey("c", created.Add(2*time.Hour)), | |||
| 106 | + | newPasskey("b", created), | |||
| 107 | + | newPasskey("a", created), | |||
| 108 | + | newPasskey("d", created.Add(time.Hour)), | |||
| 109 | + | } { | |||
| 110 | + | require.NoError(t, ps.Create(ctx, p)) | |||
| 111 | + | } | |||
| 112 | + | list, err := ps.List(ctx) | |||
| 113 | + | require.NoError(t, err) | |||
| 114 | + | var ids []string | |||
| 115 | + | for _, p := range list { | |||
| 116 | + | ids = append(ids, string(p.ID)) | |||
| 117 | + | } | |||
| 118 | + | assert.Equal(t, []string{"a", "b", "d", "c"}, ids) | |||
| 119 | + | } | |||
| 120 | + | ||||
| 121 | + | // The handle is generated once, stays stable across a restart, and concurrent | |||
| 122 | + | // first calls agree on it. | |||
| 123 | + | func TestPasskeysUserHandle(t *testing.T) { | |||
| 124 | + | ctx := context.Background() | |||
| 125 | + | dir := t.TempDir() | |||
| 126 | + | s := openStore(t, dir) | |||
| 127 | + | ||||
| 128 | + | var wg sync.WaitGroup | |||
| 129 | + | handles := make([][]byte, 8) | |||
| 130 | + | for i := range handles { | |||
| 131 | + | wg.Add(1) | |||
| 132 | + | go func() { | |||
| 133 | + | defer wg.Done() | |||
| 134 | + | h, err := s.Passkeys().UserHandle(ctx) | |||
| 135 | + | assert.NoError(t, err) | |||
| 136 | + | handles[i] = h | |||
| 137 | + | }() | |||
| 138 | + | } | |||
| 139 | + | wg.Wait() | |||
| 140 | + | handle := handles[0] | |||
| 141 | + | assert.Len(t, handle, 32) | |||
| 142 | + | for _, h := range handles[1:] { | |||
| 143 | + | assert.Equal(t, handle, h) | |||
| 144 | + | } | |||
| 145 | + | require.NoError(t, s.Close(ctx)) | |||
| 146 | + | ||||
| 147 | + | s = openStore(t, dir) | |||
| 148 | + | again, err := s.Passkeys().UserHandle(ctx) | |||
| 149 | + | require.NoError(t, err) | |||
| 150 | + | assert.Equal(t, handle, again, "a restart keeps the handle") | |||
| 151 | + | } | |||
A internal/ports/mocks/PasskeyStore.go +409 -0
| @@ -0,0 +1,409 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | "bnema.dev/ermite/internal/domain" | |
| 11 | + | mock "github.com/stretchr/testify/mock" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // NewPasskeyStore creates a new instance of PasskeyStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 15 | + | // The first argument is typically a *testing.T value. | |
| 16 | + | func NewPasskeyStore(t interface { | |
| 17 | + | mock.TestingT | |
| 18 | + | Cleanup(func()) | |
| 19 | + | }) *PasskeyStore { | |
| 20 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 21 | + | helper.Helper() | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | mock := &PasskeyStore{} | |
| 25 | + | mock.Mock.Test(t) | |
| 26 | + | ||
| 27 | + | t.Cleanup(func() { | |
| 28 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 29 | + | helper.Helper() | |
| 30 | + | } | |
| 31 | + | mock.AssertExpectations(t) | |
| 32 | + | }) | |
| 33 | + | ||
| 34 | + | return mock | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // PasskeyStore is an autogenerated mock type for the PasskeyStore type | |
| 38 | + | type PasskeyStore struct { | |
| 39 | + | mock.Mock | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | type PasskeyStore_Expecter struct { | |
| 43 | + | mock *mock.Mock | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | func (_m *PasskeyStore) EXPECT() *PasskeyStore_Expecter { | |
| 47 | + | return &PasskeyStore_Expecter{mock: &_m.Mock} | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // Create provides a mock function for the type PasskeyStore | |
| 51 | + | func (_mock *PasskeyStore) Create(ctx context.Context, p domain.Passkey) error { | |
| 52 | + | ret := _mock.Called(ctx, p) | |
| 53 | + | ||
| 54 | + | if len(ret) == 0 { | |
| 55 | + | panic("no return value specified for Create") | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | var r0 error | |
| 59 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Passkey) error); ok { | |
| 60 | + | r0 = returnFunc(ctx, p) | |
| 61 | + | } else { | |
| 62 | + | r0 = ret.Error(0) | |
| 63 | + | } | |
| 64 | + | return r0 | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // PasskeyStore_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' | |
| 68 | + | type PasskeyStore_Create_Call struct { | |
| 69 | + | *mock.Call | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | // Create is a helper method to define mock.On call | |
| 73 | + | // - ctx context.Context | |
| 74 | + | // - p domain.Passkey | |
| 75 | + | func (_e *PasskeyStore_Expecter) Create(ctx any, p any) *PasskeyStore_Create_Call { | |
| 76 | + | return &PasskeyStore_Create_Call{Call: _e.mock.On("Create", ctx, p)} | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | func (_c *PasskeyStore_Create_Call) Run(run func(ctx context.Context, p domain.Passkey)) *PasskeyStore_Create_Call { | |
| 80 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 81 | + | var arg0 context.Context | |
| 82 | + | if args[0] != nil { | |
| 83 | + | arg0 = args[0].(context.Context) | |
| 84 | + | } | |
| 85 | + | var arg1 domain.Passkey | |
| 86 | + | if args[1] != nil { | |
| 87 | + | arg1 = args[1].(domain.Passkey) | |
| 88 | + | } | |
| 89 | + | run( | |
| 90 | + | arg0, | |
| 91 | + | arg1, | |
| 92 | + | ) | |
| 93 | + | }) | |
| 94 | + | return _c | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | func (_c *PasskeyStore_Create_Call) Return(err error) *PasskeyStore_Create_Call { | |
| 98 | + | _c.Call.Return(err) | |
| 99 | + | return _c | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | func (_c *PasskeyStore_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Passkey) error) *PasskeyStore_Create_Call { | |
| 103 | + | _c.Call.Return(run) | |
| 104 | + | return _c | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | // Delete provides a mock function for the type PasskeyStore | |
| 108 | + | func (_mock *PasskeyStore) Delete(ctx context.Context, id []byte) error { | |
| 109 | + | ret := _mock.Called(ctx, id) | |
| 110 | + | ||
| 111 | + | if len(ret) == 0 { | |
| 112 | + | panic("no return value specified for Delete") | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | var r0 error | |
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) error); ok { | |
| 117 | + | r0 = returnFunc(ctx, id) | |
| 118 | + | } else { | |
| 119 | + | r0 = ret.Error(0) | |
| 120 | + | } | |
| 121 | + | return r0 | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | // PasskeyStore_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' | |
| 125 | + | type PasskeyStore_Delete_Call struct { | |
| 126 | + | *mock.Call | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // Delete is a helper method to define mock.On call | |
| 130 | + | // - ctx context.Context | |
| 131 | + | // - id []byte | |
| 132 | + | func (_e *PasskeyStore_Expecter) Delete(ctx any, id any) *PasskeyStore_Delete_Call { | |
| 133 | + | return &PasskeyStore_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | func (_c *PasskeyStore_Delete_Call) Run(run func(ctx context.Context, id []byte)) *PasskeyStore_Delete_Call { | |
| 137 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 138 | + | var arg0 context.Context | |
| 139 | + | if args[0] != nil { | |
| 140 | + | arg0 = args[0].(context.Context) | |
| 141 | + | } | |
| 142 | + | var arg1 []byte | |
| 143 | + | if args[1] != nil { | |
| 144 | + | arg1 = args[1].([]byte) | |
| 145 | + | } | |
| 146 | + | run( | |
| 147 | + | arg0, | |
| 148 | + | arg1, | |
| 149 | + | ) | |
| 150 | + | }) | |
| 151 | + | return _c | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | func (_c *PasskeyStore_Delete_Call) Return(err error) *PasskeyStore_Delete_Call { | |
| 155 | + | _c.Call.Return(err) | |
| 156 | + | return _c | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func (_c *PasskeyStore_Delete_Call) RunAndReturn(run func(ctx context.Context, id []byte) error) *PasskeyStore_Delete_Call { | |
| 160 | + | _c.Call.Return(run) | |
| 161 | + | return _c | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // Get provides a mock function for the type PasskeyStore | |
| 165 | + | func (_mock *PasskeyStore) Get(ctx context.Context, id []byte) (domain.Passkey, error) { | |
| 166 | + | ret := _mock.Called(ctx, id) | |
| 167 | + | ||
| 168 | + | if len(ret) == 0 { | |
| 169 | + | panic("no return value specified for Get") | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | var r0 domain.Passkey | |
| 173 | + | var r1 error | |
| 174 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) (domain.Passkey, error)); ok { | |
| 175 | + | return returnFunc(ctx, id) | |
| 176 | + | } | |
| 177 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) domain.Passkey); ok { | |
| 178 | + | r0 = returnFunc(ctx, id) | |
| 179 | + | } else { | |
| 180 | + | r0 = ret.Get(0).(domain.Passkey) | |
| 181 | + | } | |
| 182 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, []byte) error); ok { | |
| 183 | + | r1 = returnFunc(ctx, id) | |
| 184 | + | } else { | |
| 185 | + | r1 = ret.Error(1) | |
| 186 | + | } | |
| 187 | + | return r0, r1 | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | // PasskeyStore_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' | |
| 191 | + | type PasskeyStore_Get_Call struct { | |
| 192 | + | *mock.Call | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | // Get is a helper method to define mock.On call | |
| 196 | + | // - ctx context.Context | |
| 197 | + | // - id []byte | |
| 198 | + | func (_e *PasskeyStore_Expecter) Get(ctx any, id any) *PasskeyStore_Get_Call { | |
| 199 | + | return &PasskeyStore_Get_Call{Call: _e.mock.On("Get", ctx, id)} | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | func (_c *PasskeyStore_Get_Call) Run(run func(ctx context.Context, id []byte)) *PasskeyStore_Get_Call { | |
| 203 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 204 | + | var arg0 context.Context | |
| 205 | + | if args[0] != nil { | |
| 206 | + | arg0 = args[0].(context.Context) | |
| 207 | + | } | |
| 208 | + | var arg1 []byte | |
| 209 | + | if args[1] != nil { | |
| 210 | + | arg1 = args[1].([]byte) | |
| 211 | + | } | |
| 212 | + | run( | |
| 213 | + | arg0, | |
| 214 | + | arg1, | |
| 215 | + | ) | |
| 216 | + | }) | |
| 217 | + | return _c | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | func (_c *PasskeyStore_Get_Call) Return(passkey domain.Passkey, err error) *PasskeyStore_Get_Call { | |
| 221 | + | _c.Call.Return(passkey, err) | |
| 222 | + | return _c | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | func (_c *PasskeyStore_Get_Call) RunAndReturn(run func(ctx context.Context, id []byte) (domain.Passkey, error)) *PasskeyStore_Get_Call { | |
| 226 | + | _c.Call.Return(run) | |
| 227 | + | return _c | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | // List provides a mock function for the type PasskeyStore | |
| 231 | + | func (_mock *PasskeyStore) List(ctx context.Context) ([]domain.Passkey, error) { | |
| 232 | + | ret := _mock.Called(ctx) | |
| 233 | + | ||
| 234 | + | if len(ret) == 0 { | |
| 235 | + | panic("no return value specified for List") | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | var r0 []domain.Passkey | |
| 239 | + | var r1 error | |
| 240 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]domain.Passkey, error)); ok { | |
| 241 | + | return returnFunc(ctx) | |
| 242 | + | } | |
| 243 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []domain.Passkey); ok { | |
| 244 | + | r0 = returnFunc(ctx) | |
| 245 | + | } else { | |
| 246 | + | if ret.Get(0) != nil { | |
| 247 | + | r0 = ret.Get(0).([]domain.Passkey) | |
| 248 | + | } | |
| 249 | + | } | |
| 250 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 251 | + | r1 = returnFunc(ctx) | |
| 252 | + | } else { | |
| 253 | + | r1 = ret.Error(1) | |
| 254 | + | } | |
| 255 | + | return r0, r1 | |
| 256 | + | } | |
| 257 | + | ||
| 258 | + | // PasskeyStore_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' | |
| 259 | + | type PasskeyStore_List_Call struct { | |
| 260 | + | *mock.Call | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | // List is a helper method to define mock.On call | |
| 264 | + | // - ctx context.Context | |
| 265 | + | func (_e *PasskeyStore_Expecter) List(ctx any) *PasskeyStore_List_Call { | |
| 266 | + | return &PasskeyStore_List_Call{Call: _e.mock.On("List", ctx)} | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | func (_c *PasskeyStore_List_Call) Run(run func(ctx context.Context)) *PasskeyStore_List_Call { | |
| 270 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 271 | + | var arg0 context.Context | |
| 272 | + | if args[0] != nil { | |
| 273 | + | arg0 = args[0].(context.Context) | |
| 274 | + | } | |
| 275 | + | run( | |
| 276 | + | arg0, | |
| 277 | + | ) | |
| 278 | + | }) | |
| 279 | + | return _c | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | func (_c *PasskeyStore_List_Call) Return(passkeys []domain.Passkey, err error) *PasskeyStore_List_Call { | |
| 283 | + | _c.Call.Return(passkeys, err) | |
| 284 | + | return _c | |
| 285 | + | } | |
| 286 | + | ||
| 287 | + | func (_c *PasskeyStore_List_Call) RunAndReturn(run func(ctx context.Context) ([]domain.Passkey, error)) *PasskeyStore_List_Call { | |
| 288 | + | _c.Call.Return(run) | |
| 289 | + | return _c | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | // Update provides a mock function for the type PasskeyStore | |
| 293 | + | func (_mock *PasskeyStore) Update(ctx context.Context, p domain.Passkey) error { | |
| 294 | + | ret := _mock.Called(ctx, p) | |
| 295 | + | ||
| 296 | + | if len(ret) == 0 { | |
| 297 | + | panic("no return value specified for Update") | |
| 298 | + | } | |
| 299 | + | ||
| 300 | + | var r0 error | |
| 301 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Passkey) error); ok { | |
| 302 | + | r0 = returnFunc(ctx, p) | |
| 303 | + | } else { | |
| 304 | + | r0 = ret.Error(0) | |
| 305 | + | } | |
| 306 | + | return r0 | |
| 307 | + | } | |
| 308 | + | ||
| 309 | + | // PasskeyStore_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' | |
| 310 | + | type PasskeyStore_Update_Call struct { | |
| 311 | + | *mock.Call | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | // Update is a helper method to define mock.On call | |
| 315 | + | // - ctx context.Context | |
| 316 | + | // - p domain.Passkey | |
| 317 | + | func (_e *PasskeyStore_Expecter) Update(ctx any, p any) *PasskeyStore_Update_Call { | |
| 318 | + | return &PasskeyStore_Update_Call{Call: _e.mock.On("Update", ctx, p)} | |
| 319 | + | } | |
| 320 | + | ||
| 321 | + | func (_c *PasskeyStore_Update_Call) Run(run func(ctx context.Context, p domain.Passkey)) *PasskeyStore_Update_Call { | |
| 322 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 323 | + | var arg0 context.Context | |
| 324 | + | if args[0] != nil { | |
| 325 | + | arg0 = args[0].(context.Context) | |
| 326 | + | } | |
| 327 | + | var arg1 domain.Passkey | |
| 328 | + | if args[1] != nil { | |
| 329 | + | arg1 = args[1].(domain.Passkey) | |
| 330 | + | } | |
| 331 | + | run( | |
| 332 | + | arg0, | |
| 333 | + | arg1, | |
| 334 | + | ) | |
| 335 | + | }) | |
| 336 | + | return _c | |
| 337 | + | } | |
| 338 | + | ||
| 339 | + | func (_c *PasskeyStore_Update_Call) Return(err error) *PasskeyStore_Update_Call { | |
| 340 | + | _c.Call.Return(err) | |
| 341 | + | return _c | |
| 342 | + | } | |
| 343 | + | ||
| 344 | + | func (_c *PasskeyStore_Update_Call) RunAndReturn(run func(ctx context.Context, p domain.Passkey) error) *PasskeyStore_Update_Call { | |
| 345 | + | _c.Call.Return(run) | |
| 346 | + | return _c | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | // UserHandle provides a mock function for the type PasskeyStore | |
| 350 | + | func (_mock *PasskeyStore) UserHandle(ctx context.Context) ([]byte, error) { | |
| 351 | + | ret := _mock.Called(ctx) | |
| 352 | + | ||
| 353 | + | if len(ret) == 0 { | |
| 354 | + | panic("no return value specified for UserHandle") | |
| 355 | + | } | |
| 356 | + | ||
| 357 | + | var r0 []byte | |
| 358 | + | var r1 error | |
| 359 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok { | |
| 360 | + | return returnFunc(ctx) | |
| 361 | + | } | |
| 362 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []byte); ok { | |
| 363 | + | r0 = returnFunc(ctx) | |
| 364 | + | } else { | |
| 365 | + | if ret.Get(0) != nil { | |
| 366 | + | r0 = ret.Get(0).([]byte) | |
| 367 | + | } | |
| 368 | + | } | |
| 369 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 370 | + | r1 = returnFunc(ctx) | |
| 371 | + | } else { | |
| 372 | + | r1 = ret.Error(1) | |
| 373 | + | } | |
| 374 | + | return r0, r1 | |
| 375 | + | } | |
| 376 | + | ||
| 377 | + | // PasskeyStore_UserHandle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserHandle' | |
| 378 | + | type PasskeyStore_UserHandle_Call struct { | |
| 379 | + | *mock.Call | |
| 380 | + | } | |
| 381 | + | ||
| 382 | + | // UserHandle is a helper method to define mock.On call | |
| 383 | + | // - ctx context.Context | |
| 384 | + | func (_e *PasskeyStore_Expecter) UserHandle(ctx any) *PasskeyStore_UserHandle_Call { | |
| 385 | + | return &PasskeyStore_UserHandle_Call{Call: _e.mock.On("UserHandle", ctx)} | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | func (_c *PasskeyStore_UserHandle_Call) Run(run func(ctx context.Context)) *PasskeyStore_UserHandle_Call { | |
| 389 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 390 | + | var arg0 context.Context | |
| 391 | + | if args[0] != nil { | |
| 392 | + | arg0 = args[0].(context.Context) | |
| 393 | + | } | |
| 394 | + | run( | |
| 395 | + | arg0, | |
| 396 | + | ) | |
| 397 | + | }) | |
| 398 | + | return _c | |
| 399 | + | } | |
| 400 | + | ||
| 401 | + | func (_c *PasskeyStore_UserHandle_Call) Return(bytes []byte, err error) *PasskeyStore_UserHandle_Call { | |
| 402 | + | _c.Call.Return(bytes, err) | |
| 403 | + | return _c | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | func (_c *PasskeyStore_UserHandle_Call) RunAndReturn(run func(ctx context.Context) ([]byte, error)) *PasskeyStore_UserHandle_Call { | |
| 407 | + | _c.Call.Return(run) | |
| 408 | + | return _c | |
| 409 | + | } | |
| @@ -0,0 +1,409 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | "bnema.dev/ermite/internal/domain" | |||
| 11 | + | mock "github.com/stretchr/testify/mock" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // NewPasskeyStore creates a new instance of PasskeyStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 15 | + | // The first argument is typically a *testing.T value. | |||
| 16 | + | func NewPasskeyStore(t interface { | |||
| 17 | + | mock.TestingT | |||
| 18 | + | Cleanup(func()) | |||
| 19 | + | }) *PasskeyStore { | |||
| 20 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 21 | + | helper.Helper() | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | mock := &PasskeyStore{} | |||
| 25 | + | mock.Mock.Test(t) | |||
| 26 | + | ||||
| 27 | + | t.Cleanup(func() { | |||
| 28 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 29 | + | helper.Helper() | |||
| 30 | + | } | |||
| 31 | + | mock.AssertExpectations(t) | |||
| 32 | + | }) | |||
| 33 | + | ||||
| 34 | + | return mock | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // PasskeyStore is an autogenerated mock type for the PasskeyStore type | |||
| 38 | + | type PasskeyStore struct { | |||
| 39 | + | mock.Mock | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | type PasskeyStore_Expecter struct { | |||
| 43 | + | mock *mock.Mock | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | func (_m *PasskeyStore) EXPECT() *PasskeyStore_Expecter { | |||
| 47 | + | return &PasskeyStore_Expecter{mock: &_m.Mock} | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // Create provides a mock function for the type PasskeyStore | |||
| 51 | + | func (_mock *PasskeyStore) Create(ctx context.Context, p domain.Passkey) error { | |||
| 52 | + | ret := _mock.Called(ctx, p) | |||
| 53 | + | ||||
| 54 | + | if len(ret) == 0 { | |||
| 55 | + | panic("no return value specified for Create") | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | var r0 error | |||
| 59 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Passkey) error); ok { | |||
| 60 | + | r0 = returnFunc(ctx, p) | |||
| 61 | + | } else { | |||
| 62 | + | r0 = ret.Error(0) | |||
| 63 | + | } | |||
| 64 | + | return r0 | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | // PasskeyStore_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' | |||
| 68 | + | type PasskeyStore_Create_Call struct { | |||
| 69 | + | *mock.Call | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | // Create is a helper method to define mock.On call | |||
| 73 | + | // - ctx context.Context | |||
| 74 | + | // - p domain.Passkey | |||
| 75 | + | func (_e *PasskeyStore_Expecter) Create(ctx any, p any) *PasskeyStore_Create_Call { | |||
| 76 | + | return &PasskeyStore_Create_Call{Call: _e.mock.On("Create", ctx, p)} | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | func (_c *PasskeyStore_Create_Call) Run(run func(ctx context.Context, p domain.Passkey)) *PasskeyStore_Create_Call { | |||
| 80 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 81 | + | var arg0 context.Context | |||
| 82 | + | if args[0] != nil { | |||
| 83 | + | arg0 = args[0].(context.Context) | |||
| 84 | + | } | |||
| 85 | + | var arg1 domain.Passkey | |||
| 86 | + | if args[1] != nil { | |||
| 87 | + | arg1 = args[1].(domain.Passkey) | |||
| 88 | + | } | |||
| 89 | + | run( | |||
| 90 | + | arg0, | |||
| 91 | + | arg1, | |||
| 92 | + | ) | |||
| 93 | + | }) | |||
| 94 | + | return _c | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | func (_c *PasskeyStore_Create_Call) Return(err error) *PasskeyStore_Create_Call { | |||
| 98 | + | _c.Call.Return(err) | |||
| 99 | + | return _c | |||
| 100 | + | } | |||
| 101 | + | ||||
| 102 | + | func (_c *PasskeyStore_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Passkey) error) *PasskeyStore_Create_Call { | |||
| 103 | + | _c.Call.Return(run) | |||
| 104 | + | return _c | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | // Delete provides a mock function for the type PasskeyStore | |||
| 108 | + | func (_mock *PasskeyStore) Delete(ctx context.Context, id []byte) error { | |||
| 109 | + | ret := _mock.Called(ctx, id) | |||
| 110 | + | ||||
| 111 | + | if len(ret) == 0 { | |||
| 112 | + | panic("no return value specified for Delete") | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | var r0 error | |||
| 116 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) error); ok { | |||
| 117 | + | r0 = returnFunc(ctx, id) | |||
| 118 | + | } else { | |||
| 119 | + | r0 = ret.Error(0) | |||
| 120 | + | } | |||
| 121 | + | return r0 | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | // PasskeyStore_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' | |||
| 125 | + | type PasskeyStore_Delete_Call struct { | |||
| 126 | + | *mock.Call | |||
| 127 | + | } | |||
| 128 | + | ||||
| 129 | + | // Delete is a helper method to define mock.On call | |||
| 130 | + | // - ctx context.Context | |||
| 131 | + | // - id []byte | |||
| 132 | + | func (_e *PasskeyStore_Expecter) Delete(ctx any, id any) *PasskeyStore_Delete_Call { | |||
| 133 | + | return &PasskeyStore_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | func (_c *PasskeyStore_Delete_Call) Run(run func(ctx context.Context, id []byte)) *PasskeyStore_Delete_Call { | |||
| 137 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 138 | + | var arg0 context.Context | |||
| 139 | + | if args[0] != nil { | |||
| 140 | + | arg0 = args[0].(context.Context) | |||
| 141 | + | } | |||
| 142 | + | var arg1 []byte | |||
| 143 | + | if args[1] != nil { | |||
| 144 | + | arg1 = args[1].([]byte) | |||
| 145 | + | } | |||
| 146 | + | run( | |||
| 147 | + | arg0, | |||
| 148 | + | arg1, | |||
| 149 | + | ) | |||
| 150 | + | }) | |||
| 151 | + | return _c | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | func (_c *PasskeyStore_Delete_Call) Return(err error) *PasskeyStore_Delete_Call { | |||
| 155 | + | _c.Call.Return(err) | |||
| 156 | + | return _c | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | func (_c *PasskeyStore_Delete_Call) RunAndReturn(run func(ctx context.Context, id []byte) error) *PasskeyStore_Delete_Call { | |||
| 160 | + | _c.Call.Return(run) | |||
| 161 | + | return _c | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // Get provides a mock function for the type PasskeyStore | |||
| 165 | + | func (_mock *PasskeyStore) Get(ctx context.Context, id []byte) (domain.Passkey, error) { | |||
| 166 | + | ret := _mock.Called(ctx, id) | |||
| 167 | + | ||||
| 168 | + | if len(ret) == 0 { | |||
| 169 | + | panic("no return value specified for Get") | |||
| 170 | + | } | |||
| 171 | + | ||||
| 172 | + | var r0 domain.Passkey | |||
| 173 | + | var r1 error | |||
| 174 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) (domain.Passkey, error)); ok { | |||
| 175 | + | return returnFunc(ctx, id) | |||
| 176 | + | } | |||
| 177 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []byte) domain.Passkey); ok { | |||
| 178 | + | r0 = returnFunc(ctx, id) | |||
| 179 | + | } else { | |||
| 180 | + | r0 = ret.Get(0).(domain.Passkey) | |||
| 181 | + | } | |||
| 182 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, []byte) error); ok { | |||
| 183 | + | r1 = returnFunc(ctx, id) | |||
| 184 | + | } else { | |||
| 185 | + | r1 = ret.Error(1) | |||
| 186 | + | } | |||
| 187 | + | return r0, r1 | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | // PasskeyStore_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' | |||
| 191 | + | type PasskeyStore_Get_Call struct { | |||
| 192 | + | *mock.Call | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | // Get is a helper method to define mock.On call | |||
| 196 | + | // - ctx context.Context | |||
| 197 | + | // - id []byte | |||
| 198 | + | func (_e *PasskeyStore_Expecter) Get(ctx any, id any) *PasskeyStore_Get_Call { | |||
| 199 | + | return &PasskeyStore_Get_Call{Call: _e.mock.On("Get", ctx, id)} | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | func (_c *PasskeyStore_Get_Call) Run(run func(ctx context.Context, id []byte)) *PasskeyStore_Get_Call { | |||
| 203 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 204 | + | var arg0 context.Context | |||
| 205 | + | if args[0] != nil { | |||
| 206 | + | arg0 = args[0].(context.Context) | |||
| 207 | + | } | |||
| 208 | + | var arg1 []byte | |||
| 209 | + | if args[1] != nil { | |||
| 210 | + | arg1 = args[1].([]byte) | |||
| 211 | + | } | |||
| 212 | + | run( | |||
| 213 | + | arg0, | |||
| 214 | + | arg1, | |||
| 215 | + | ) | |||
| 216 | + | }) | |||
| 217 | + | return _c | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | func (_c *PasskeyStore_Get_Call) Return(passkey domain.Passkey, err error) *PasskeyStore_Get_Call { | |||
| 221 | + | _c.Call.Return(passkey, err) | |||
| 222 | + | return _c | |||
| 223 | + | } | |||
| 224 | + | ||||
| 225 | + | func (_c *PasskeyStore_Get_Call) RunAndReturn(run func(ctx context.Context, id []byte) (domain.Passkey, error)) *PasskeyStore_Get_Call { | |||
| 226 | + | _c.Call.Return(run) | |||
| 227 | + | return _c | |||
| 228 | + | } | |||
| 229 | + | ||||
| 230 | + | // List provides a mock function for the type PasskeyStore | |||
| 231 | + | func (_mock *PasskeyStore) List(ctx context.Context) ([]domain.Passkey, error) { | |||
| 232 | + | ret := _mock.Called(ctx) | |||
| 233 | + | ||||
| 234 | + | if len(ret) == 0 { | |||
| 235 | + | panic("no return value specified for List") | |||
| 236 | + | } | |||
| 237 | + | ||||
| 238 | + | var r0 []domain.Passkey | |||
| 239 | + | var r1 error | |||
| 240 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]domain.Passkey, error)); ok { | |||
| 241 | + | return returnFunc(ctx) | |||
| 242 | + | } | |||
| 243 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []domain.Passkey); ok { | |||
| 244 | + | r0 = returnFunc(ctx) | |||
| 245 | + | } else { | |||
| 246 | + | if ret.Get(0) != nil { | |||
| 247 | + | r0 = ret.Get(0).([]domain.Passkey) | |||
| 248 | + | } | |||
| 249 | + | } | |||
| 250 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 251 | + | r1 = returnFunc(ctx) | |||
| 252 | + | } else { | |||
| 253 | + | r1 = ret.Error(1) | |||
| 254 | + | } | |||
| 255 | + | return r0, r1 | |||
| 256 | + | } | |||
| 257 | + | ||||
| 258 | + | // PasskeyStore_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' | |||
| 259 | + | type PasskeyStore_List_Call struct { | |||
| 260 | + | *mock.Call | |||
| 261 | + | } | |||
| 262 | + | ||||
| 263 | + | // List is a helper method to define mock.On call | |||
| 264 | + | // - ctx context.Context | |||
| 265 | + | func (_e *PasskeyStore_Expecter) List(ctx any) *PasskeyStore_List_Call { | |||
| 266 | + | return &PasskeyStore_List_Call{Call: _e.mock.On("List", ctx)} | |||
| 267 | + | } | |||
| 268 | + | ||||
| 269 | + | func (_c *PasskeyStore_List_Call) Run(run func(ctx context.Context)) *PasskeyStore_List_Call { | |||
| 270 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 271 | + | var arg0 context.Context | |||
| 272 | + | if args[0] != nil { | |||
| 273 | + | arg0 = args[0].(context.Context) | |||
| 274 | + | } | |||
| 275 | + | run( | |||
| 276 | + | arg0, | |||
| 277 | + | ) | |||
| 278 | + | }) | |||
| 279 | + | return _c | |||
| 280 | + | } | |||
| 281 | + | ||||
| 282 | + | func (_c *PasskeyStore_List_Call) Return(passkeys []domain.Passkey, err error) *PasskeyStore_List_Call { | |||
| 283 | + | _c.Call.Return(passkeys, err) | |||
| 284 | + | return _c | |||
| 285 | + | } | |||
| 286 | + | ||||
| 287 | + | func (_c *PasskeyStore_List_Call) RunAndReturn(run func(ctx context.Context) ([]domain.Passkey, error)) *PasskeyStore_List_Call { | |||
| 288 | + | _c.Call.Return(run) | |||
| 289 | + | return _c | |||
| 290 | + | } | |||
| 291 | + | ||||
| 292 | + | // Update provides a mock function for the type PasskeyStore | |||
| 293 | + | func (_mock *PasskeyStore) Update(ctx context.Context, p domain.Passkey) error { | |||
| 294 | + | ret := _mock.Called(ctx, p) | |||
| 295 | + | ||||
| 296 | + | if len(ret) == 0 { | |||
| 297 | + | panic("no return value specified for Update") | |||
| 298 | + | } | |||
| 299 | + | ||||
| 300 | + | var r0 error | |||
| 301 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Passkey) error); ok { | |||
| 302 | + | r0 = returnFunc(ctx, p) | |||
| 303 | + | } else { | |||
| 304 | + | r0 = ret.Error(0) | |||
| 305 | + | } | |||
| 306 | + | return r0 | |||
| 307 | + | } | |||
| 308 | + | ||||
| 309 | + | // PasskeyStore_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' | |||
| 310 | + | type PasskeyStore_Update_Call struct { | |||
| 311 | + | *mock.Call | |||
| 312 | + | } | |||
| 313 | + | ||||
| 314 | + | // Update is a helper method to define mock.On call | |||
| 315 | + | // - ctx context.Context | |||
| 316 | + | // - p domain.Passkey | |||
| 317 | + | func (_e *PasskeyStore_Expecter) Update(ctx any, p any) *PasskeyStore_Update_Call { | |||
| 318 | + | return &PasskeyStore_Update_Call{Call: _e.mock.On("Update", ctx, p)} | |||
| 319 | + | } | |||
| 320 | + | ||||
| 321 | + | func (_c *PasskeyStore_Update_Call) Run(run func(ctx context.Context, p domain.Passkey)) *PasskeyStore_Update_Call { | |||
| 322 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 323 | + | var arg0 context.Context | |||
| 324 | + | if args[0] != nil { | |||
| 325 | + | arg0 = args[0].(context.Context) | |||
| 326 | + | } | |||
| 327 | + | var arg1 domain.Passkey | |||
| 328 | + | if args[1] != nil { | |||
| 329 | + | arg1 = args[1].(domain.Passkey) | |||
| 330 | + | } | |||
| 331 | + | run( | |||
| 332 | + | arg0, | |||
| 333 | + | arg1, | |||
| 334 | + | ) | |||
| 335 | + | }) | |||
| 336 | + | return _c | |||
| 337 | + | } | |||
| 338 | + | ||||
| 339 | + | func (_c *PasskeyStore_Update_Call) Return(err error) *PasskeyStore_Update_Call { | |||
| 340 | + | _c.Call.Return(err) | |||
| 341 | + | return _c | |||
| 342 | + | } | |||
| 343 | + | ||||
| 344 | + | func (_c *PasskeyStore_Update_Call) RunAndReturn(run func(ctx context.Context, p domain.Passkey) error) *PasskeyStore_Update_Call { | |||
| 345 | + | _c.Call.Return(run) | |||
| 346 | + | return _c | |||
| 347 | + | } | |||
| 348 | + | ||||
| 349 | + | // UserHandle provides a mock function for the type PasskeyStore | |||
| 350 | + | func (_mock *PasskeyStore) UserHandle(ctx context.Context) ([]byte, error) { | |||
| 351 | + | ret := _mock.Called(ctx) | |||
| 352 | + | ||||
| 353 | + | if len(ret) == 0 { | |||
| 354 | + | panic("no return value specified for UserHandle") | |||
| 355 | + | } | |||
| 356 | + | ||||
| 357 | + | var r0 []byte | |||
| 358 | + | var r1 error | |||
| 359 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok { | |||
| 360 | + | return returnFunc(ctx) | |||
| 361 | + | } | |||
| 362 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []byte); ok { | |||
| 363 | + | r0 = returnFunc(ctx) | |||
| 364 | + | } else { | |||
| 365 | + | if ret.Get(0) != nil { | |||
| 366 | + | r0 = ret.Get(0).([]byte) | |||
| 367 | + | } | |||
| 368 | + | } | |||
| 369 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 370 | + | r1 = returnFunc(ctx) | |||
| 371 | + | } else { | |||
| 372 | + | r1 = ret.Error(1) | |||
| 373 | + | } | |||
| 374 | + | return r0, r1 | |||
| 375 | + | } | |||
| 376 | + | ||||
| 377 | + | // PasskeyStore_UserHandle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserHandle' | |||
| 378 | + | type PasskeyStore_UserHandle_Call struct { | |||
| 379 | + | *mock.Call | |||
| 380 | + | } | |||
| 381 | + | ||||
| 382 | + | // UserHandle is a helper method to define mock.On call | |||
| 383 | + | // - ctx context.Context | |||
| 384 | + | func (_e *PasskeyStore_Expecter) UserHandle(ctx any) *PasskeyStore_UserHandle_Call { | |||
| 385 | + | return &PasskeyStore_UserHandle_Call{Call: _e.mock.On("UserHandle", ctx)} | |||
| 386 | + | } | |||
| 387 | + | ||||
| 388 | + | func (_c *PasskeyStore_UserHandle_Call) Run(run func(ctx context.Context)) *PasskeyStore_UserHandle_Call { | |||
| 389 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 390 | + | var arg0 context.Context | |||
| 391 | + | if args[0] != nil { | |||
| 392 | + | arg0 = args[0].(context.Context) | |||
| 393 | + | } | |||
| 394 | + | run( | |||
| 395 | + | arg0, | |||
| 396 | + | ) | |||
| 397 | + | }) | |||
| 398 | + | return _c | |||
| 399 | + | } | |||
| 400 | + | ||||
| 401 | + | func (_c *PasskeyStore_UserHandle_Call) Return(bytes []byte, err error) *PasskeyStore_UserHandle_Call { | |||
| 402 | + | _c.Call.Return(bytes, err) | |||
| 403 | + | return _c | |||
| 404 | + | } | |||
| 405 | + | ||||
| 406 | + | func (_c *PasskeyStore_UserHandle_Call) RunAndReturn(run func(ctx context.Context) ([]byte, error)) *PasskeyStore_UserHandle_Call { | |||
| 407 | + | _c.Call.Return(run) | |||
| 408 | + | return _c | |||
| 409 | + | } | |||
A internal/ports/mocks/PasskeyVerifier.go +335 -0
| @@ -0,0 +1,335 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "bnema.dev/ermite/internal/domain" | |
| 9 | + | mock "github.com/stretchr/testify/mock" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // NewPasskeyVerifier creates a new instance of PasskeyVerifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 13 | + | // The first argument is typically a *testing.T value. | |
| 14 | + | func NewPasskeyVerifier(t interface { | |
| 15 | + | mock.TestingT | |
| 16 | + | Cleanup(func()) | |
| 17 | + | }) *PasskeyVerifier { | |
| 18 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 19 | + | helper.Helper() | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | mock := &PasskeyVerifier{} | |
| 23 | + | mock.Mock.Test(t) | |
| 24 | + | ||
| 25 | + | t.Cleanup(func() { | |
| 26 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 27 | + | helper.Helper() | |
| 28 | + | } | |
| 29 | + | mock.AssertExpectations(t) | |
| 30 | + | }) | |
| 31 | + | ||
| 32 | + | return mock | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | // PasskeyVerifier is an autogenerated mock type for the PasskeyVerifier type | |
| 36 | + | type PasskeyVerifier struct { | |
| 37 | + | mock.Mock | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | type PasskeyVerifier_Expecter struct { | |
| 41 | + | mock *mock.Mock | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | func (_m *PasskeyVerifier) EXPECT() *PasskeyVerifier_Expecter { | |
| 45 | + | return &PasskeyVerifier_Expecter{mock: &_m.Mock} | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | // BeginLogin provides a mock function for the type PasskeyVerifier | |
| 49 | + | func (_mock *PasskeyVerifier) BeginLogin() ([]byte, []byte, error) { | |
| 50 | + | ret := _mock.Called() | |
| 51 | + | ||
| 52 | + | if len(ret) == 0 { | |
| 53 | + | panic("no return value specified for BeginLogin") | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | var r0 []byte | |
| 57 | + | var r1 []byte | |
| 58 | + | var r2 error | |
| 59 | + | if returnFunc, ok := ret.Get(0).(func() ([]byte, []byte, error)); ok { | |
| 60 | + | return returnFunc() | |
| 61 | + | } | |
| 62 | + | if returnFunc, ok := ret.Get(0).(func() []byte); ok { | |
| 63 | + | r0 = returnFunc() | |
| 64 | + | } else { | |
| 65 | + | if ret.Get(0) != nil { | |
| 66 | + | r0 = ret.Get(0).([]byte) | |
| 67 | + | } | |
| 68 | + | } | |
| 69 | + | if returnFunc, ok := ret.Get(1).(func() []byte); ok { | |
| 70 | + | r1 = returnFunc() | |
| 71 | + | } else { | |
| 72 | + | if ret.Get(1) != nil { | |
| 73 | + | r1 = ret.Get(1).([]byte) | |
| 74 | + | } | |
| 75 | + | } | |
| 76 | + | if returnFunc, ok := ret.Get(2).(func() error); ok { | |
| 77 | + | r2 = returnFunc() | |
| 78 | + | } else { | |
| 79 | + | r2 = ret.Error(2) | |
| 80 | + | } | |
| 81 | + | return r0, r1, r2 | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | // PasskeyVerifier_BeginLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginLogin' | |
| 85 | + | type PasskeyVerifier_BeginLogin_Call struct { | |
| 86 | + | *mock.Call | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | // BeginLogin is a helper method to define mock.On call | |
| 90 | + | func (_e *PasskeyVerifier_Expecter) BeginLogin() *PasskeyVerifier_BeginLogin_Call { | |
| 91 | + | return &PasskeyVerifier_BeginLogin_Call{Call: _e.mock.On("BeginLogin")} | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | func (_c *PasskeyVerifier_BeginLogin_Call) Run(run func()) *PasskeyVerifier_BeginLogin_Call { | |
| 95 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 96 | + | run() | |
| 97 | + | }) | |
| 98 | + | return _c | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | func (_c *PasskeyVerifier_BeginLogin_Call) Return(options []byte, ceremony []byte, err error) *PasskeyVerifier_BeginLogin_Call { | |
| 102 | + | _c.Call.Return(options, ceremony, err) | |
| 103 | + | return _c | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | func (_c *PasskeyVerifier_BeginLogin_Call) RunAndReturn(run func() ([]byte, []byte, error)) *PasskeyVerifier_BeginLogin_Call { | |
| 107 | + | _c.Call.Return(run) | |
| 108 | + | return _c | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // BeginRegistration provides a mock function for the type PasskeyVerifier | |
| 112 | + | func (_mock *PasskeyVerifier) BeginRegistration(userHandle []byte, existing []domain.Passkey) ([]byte, []byte, error) { | |
| 113 | + | ret := _mock.Called(userHandle, existing) | |
| 114 | + | ||
| 115 | + | if len(ret) == 0 { | |
| 116 | + | panic("no return value specified for BeginRegistration") | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | var r0 []byte | |
| 120 | + | var r1 []byte | |
| 121 | + | var r2 error | |
| 122 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []domain.Passkey) ([]byte, []byte, error)); ok { | |
| 123 | + | return returnFunc(userHandle, existing) | |
| 124 | + | } | |
| 125 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []domain.Passkey) []byte); ok { | |
| 126 | + | r0 = returnFunc(userHandle, existing) | |
| 127 | + | } else { | |
| 128 | + | if ret.Get(0) != nil { | |
| 129 | + | r0 = ret.Get(0).([]byte) | |
| 130 | + | } | |
| 131 | + | } | |
| 132 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []domain.Passkey) []byte); ok { | |
| 133 | + | r1 = returnFunc(userHandle, existing) | |
| 134 | + | } else { | |
| 135 | + | if ret.Get(1) != nil { | |
| 136 | + | r1 = ret.Get(1).([]byte) | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | if returnFunc, ok := ret.Get(2).(func([]byte, []domain.Passkey) error); ok { | |
| 140 | + | r2 = returnFunc(userHandle, existing) | |
| 141 | + | } else { | |
| 142 | + | r2 = ret.Error(2) | |
| 143 | + | } | |
| 144 | + | return r0, r1, r2 | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // PasskeyVerifier_BeginRegistration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginRegistration' | |
| 148 | + | type PasskeyVerifier_BeginRegistration_Call struct { | |
| 149 | + | *mock.Call | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | // BeginRegistration is a helper method to define mock.On call | |
| 153 | + | // - userHandle []byte | |
| 154 | + | // - existing []domain.Passkey | |
| 155 | + | func (_e *PasskeyVerifier_Expecter) BeginRegistration(userHandle any, existing any) *PasskeyVerifier_BeginRegistration_Call { | |
| 156 | + | return &PasskeyVerifier_BeginRegistration_Call{Call: _e.mock.On("BeginRegistration", userHandle, existing)} | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) Run(run func(userHandle []byte, existing []domain.Passkey)) *PasskeyVerifier_BeginRegistration_Call { | |
| 160 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 161 | + | var arg0 []byte | |
| 162 | + | if args[0] != nil { | |
| 163 | + | arg0 = args[0].([]byte) | |
| 164 | + | } | |
| 165 | + | var arg1 []domain.Passkey | |
| 166 | + | if args[1] != nil { | |
| 167 | + | arg1 = args[1].([]domain.Passkey) | |
| 168 | + | } | |
| 169 | + | run( | |
| 170 | + | arg0, | |
| 171 | + | arg1, | |
| 172 | + | ) | |
| 173 | + | }) | |
| 174 | + | return _c | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) Return(options []byte, ceremony []byte, err error) *PasskeyVerifier_BeginRegistration_Call { | |
| 178 | + | _c.Call.Return(options, ceremony, err) | |
| 179 | + | return _c | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) RunAndReturn(run func(userHandle []byte, existing []domain.Passkey) ([]byte, []byte, error)) *PasskeyVerifier_BeginRegistration_Call { | |
| 183 | + | _c.Call.Return(run) | |
| 184 | + | return _c | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | // FinishLogin provides a mock function for the type PasskeyVerifier | |
| 188 | + | func (_mock *PasskeyVerifier) FinishLogin(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error) { | |
| 189 | + | ret := _mock.Called(userHandle, ceremony, response, lookup) | |
| 190 | + | ||
| 191 | + | if len(ret) == 0 { | |
| 192 | + | panic("no return value specified for FinishLogin") | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | var r0 domain.Passkey | |
| 196 | + | var r1 error | |
| 197 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) (domain.Passkey, error)); ok { | |
| 198 | + | return returnFunc(userHandle, ceremony, response, lookup) | |
| 199 | + | } | |
| 200 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) domain.Passkey); ok { | |
| 201 | + | r0 = returnFunc(userHandle, ceremony, response, lookup) | |
| 202 | + | } else { | |
| 203 | + | r0 = ret.Get(0).(domain.Passkey) | |
| 204 | + | } | |
| 205 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) error); ok { | |
| 206 | + | r1 = returnFunc(userHandle, ceremony, response, lookup) | |
| 207 | + | } else { | |
| 208 | + | r1 = ret.Error(1) | |
| 209 | + | } | |
| 210 | + | return r0, r1 | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | // PasskeyVerifier_FinishLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FinishLogin' | |
| 214 | + | type PasskeyVerifier_FinishLogin_Call struct { | |
| 215 | + | *mock.Call | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | // FinishLogin is a helper method to define mock.On call | |
| 219 | + | // - userHandle []byte | |
| 220 | + | // - ceremony []byte | |
| 221 | + | // - response []byte | |
| 222 | + | // - lookup func(id []byte) (domain.Passkey, error) | |
| 223 | + | func (_e *PasskeyVerifier_Expecter) FinishLogin(userHandle any, ceremony any, response any, lookup any) *PasskeyVerifier_FinishLogin_Call { | |
| 224 | + | return &PasskeyVerifier_FinishLogin_Call{Call: _e.mock.On("FinishLogin", userHandle, ceremony, response, lookup)} | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | func (_c *PasskeyVerifier_FinishLogin_Call) Run(run func(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error))) *PasskeyVerifier_FinishLogin_Call { | |
| 228 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 229 | + | var arg0 []byte | |
| 230 | + | if args[0] != nil { | |
| 231 | + | arg0 = args[0].([]byte) | |
| 232 | + | } | |
| 233 | + | var arg1 []byte | |
| 234 | + | if args[1] != nil { | |
| 235 | + | arg1 = args[1].([]byte) | |
| 236 | + | } | |
| 237 | + | var arg2 []byte | |
| 238 | + | if args[2] != nil { | |
| 239 | + | arg2 = args[2].([]byte) | |
| 240 | + | } | |
| 241 | + | var arg3 func(id []byte) (domain.Passkey, error) | |
| 242 | + | if args[3] != nil { | |
| 243 | + | arg3 = args[3].(func(id []byte) (domain.Passkey, error)) | |
| 244 | + | } | |
| 245 | + | run( | |
| 246 | + | arg0, | |
| 247 | + | arg1, | |
| 248 | + | arg2, | |
| 249 | + | arg3, | |
| 250 | + | ) | |
| 251 | + | }) | |
| 252 | + | return _c | |
| 253 | + | } | |
| 254 | + | ||
| 255 | + | func (_c *PasskeyVerifier_FinishLogin_Call) Return(passkey domain.Passkey, err error) *PasskeyVerifier_FinishLogin_Call { | |
| 256 | + | _c.Call.Return(passkey, err) | |
| 257 | + | return _c | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | func (_c *PasskeyVerifier_FinishLogin_Call) RunAndReturn(run func(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error)) *PasskeyVerifier_FinishLogin_Call { | |
| 261 | + | _c.Call.Return(run) | |
| 262 | + | return _c | |
| 263 | + | } | |
| 264 | + | ||
| 265 | + | // FinishRegistration provides a mock function for the type PasskeyVerifier | |
| 266 | + | func (_mock *PasskeyVerifier) FinishRegistration(userHandle []byte, ceremony []byte, response []byte) (domain.Passkey, error) { | |
| 267 | + | ret := _mock.Called(userHandle, ceremony, response) | |
| 268 | + | ||
| 269 | + | if len(ret) == 0 { | |
| 270 | + | panic("no return value specified for FinishRegistration") | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | var r0 domain.Passkey | |
| 274 | + | var r1 error | |
| 275 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte) (domain.Passkey, error)); ok { | |
| 276 | + | return returnFunc(userHandle, ceremony, response) | |
| 277 | + | } | |
| 278 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte) domain.Passkey); ok { | |
| 279 | + | r0 = returnFunc(userHandle, ceremony, response) | |
| 280 | + | } else { | |
| 281 | + | r0 = ret.Get(0).(domain.Passkey) | |
| 282 | + | } | |
| 283 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []byte, []byte) error); ok { | |
| 284 | + | r1 = returnFunc(userHandle, ceremony, response) | |
| 285 | + | } else { | |
| 286 | + | r1 = ret.Error(1) | |
| 287 | + | } | |
| 288 | + | return r0, r1 | |
| 289 | + | } | |
| 290 | + | ||
| 291 | + | // PasskeyVerifier_FinishRegistration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FinishRegistration' | |
| 292 | + | type PasskeyVerifier_FinishRegistration_Call struct { | |
| 293 | + | *mock.Call | |
| 294 | + | } | |
| 295 | + | ||
| 296 | + | // FinishRegistration is a helper method to define mock.On call | |
| 297 | + | // - userHandle []byte | |
| 298 | + | // - ceremony []byte | |
| 299 | + | // - response []byte | |
| 300 | + | func (_e *PasskeyVerifier_Expecter) FinishRegistration(userHandle any, ceremony any, response any) *PasskeyVerifier_FinishRegistration_Call { | |
| 301 | + | return &PasskeyVerifier_FinishRegistration_Call{Call: _e.mock.On("FinishRegistration", userHandle, ceremony, response)} | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) Run(run func(userHandle []byte, ceremony []byte, response []byte)) *PasskeyVerifier_FinishRegistration_Call { | |
| 305 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 306 | + | var arg0 []byte | |
| 307 | + | if args[0] != nil { | |
| 308 | + | arg0 = args[0].([]byte) | |
| 309 | + | } | |
| 310 | + | var arg1 []byte | |
| 311 | + | if args[1] != nil { | |
| 312 | + | arg1 = args[1].([]byte) | |
| 313 | + | } | |
| 314 | + | var arg2 []byte | |
| 315 | + | if args[2] != nil { | |
| 316 | + | arg2 = args[2].([]byte) | |
| 317 | + | } | |
| 318 | + | run( | |
| 319 | + | arg0, | |
| 320 | + | arg1, | |
| 321 | + | arg2, | |
| 322 | + | ) | |
| 323 | + | }) | |
| 324 | + | return _c | |
| 325 | + | } | |
| 326 | + | ||
| 327 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) Return(passkey domain.Passkey, err error) *PasskeyVerifier_FinishRegistration_Call { | |
| 328 | + | _c.Call.Return(passkey, err) | |
| 329 | + | return _c | |
| 330 | + | } | |
| 331 | + | ||
| 332 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) RunAndReturn(run func(userHandle []byte, ceremony []byte, response []byte) (domain.Passkey, error)) *PasskeyVerifier_FinishRegistration_Call { | |
| 333 | + | _c.Call.Return(run) | |
| 334 | + | return _c | |
| 335 | + | } | |
| @@ -0,0 +1,335 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "bnema.dev/ermite/internal/domain" | |||
| 9 | + | mock "github.com/stretchr/testify/mock" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // NewPasskeyVerifier creates a new instance of PasskeyVerifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 13 | + | // The first argument is typically a *testing.T value. | |||
| 14 | + | func NewPasskeyVerifier(t interface { | |||
| 15 | + | mock.TestingT | |||
| 16 | + | Cleanup(func()) | |||
| 17 | + | }) *PasskeyVerifier { | |||
| 18 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 19 | + | helper.Helper() | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | mock := &PasskeyVerifier{} | |||
| 23 | + | mock.Mock.Test(t) | |||
| 24 | + | ||||
| 25 | + | t.Cleanup(func() { | |||
| 26 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 27 | + | helper.Helper() | |||
| 28 | + | } | |||
| 29 | + | mock.AssertExpectations(t) | |||
| 30 | + | }) | |||
| 31 | + | ||||
| 32 | + | return mock | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | // PasskeyVerifier is an autogenerated mock type for the PasskeyVerifier type | |||
| 36 | + | type PasskeyVerifier struct { | |||
| 37 | + | mock.Mock | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | type PasskeyVerifier_Expecter struct { | |||
| 41 | + | mock *mock.Mock | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func (_m *PasskeyVerifier) EXPECT() *PasskeyVerifier_Expecter { | |||
| 45 | + | return &PasskeyVerifier_Expecter{mock: &_m.Mock} | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | // BeginLogin provides a mock function for the type PasskeyVerifier | |||
| 49 | + | func (_mock *PasskeyVerifier) BeginLogin() ([]byte, []byte, error) { | |||
| 50 | + | ret := _mock.Called() | |||
| 51 | + | ||||
| 52 | + | if len(ret) == 0 { | |||
| 53 | + | panic("no return value specified for BeginLogin") | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | var r0 []byte | |||
| 57 | + | var r1 []byte | |||
| 58 | + | var r2 error | |||
| 59 | + | if returnFunc, ok := ret.Get(0).(func() ([]byte, []byte, error)); ok { | |||
| 60 | + | return returnFunc() | |||
| 61 | + | } | |||
| 62 | + | if returnFunc, ok := ret.Get(0).(func() []byte); ok { | |||
| 63 | + | r0 = returnFunc() | |||
| 64 | + | } else { | |||
| 65 | + | if ret.Get(0) != nil { | |||
| 66 | + | r0 = ret.Get(0).([]byte) | |||
| 67 | + | } | |||
| 68 | + | } | |||
| 69 | + | if returnFunc, ok := ret.Get(1).(func() []byte); ok { | |||
| 70 | + | r1 = returnFunc() | |||
| 71 | + | } else { | |||
| 72 | + | if ret.Get(1) != nil { | |||
| 73 | + | r1 = ret.Get(1).([]byte) | |||
| 74 | + | } | |||
| 75 | + | } | |||
| 76 | + | if returnFunc, ok := ret.Get(2).(func() error); ok { | |||
| 77 | + | r2 = returnFunc() | |||
| 78 | + | } else { | |||
| 79 | + | r2 = ret.Error(2) | |||
| 80 | + | } | |||
| 81 | + | return r0, r1, r2 | |||
| 82 | + | } | |||
| 83 | + | ||||
| 84 | + | // PasskeyVerifier_BeginLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginLogin' | |||
| 85 | + | type PasskeyVerifier_BeginLogin_Call struct { | |||
| 86 | + | *mock.Call | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | // BeginLogin is a helper method to define mock.On call | |||
| 90 | + | func (_e *PasskeyVerifier_Expecter) BeginLogin() *PasskeyVerifier_BeginLogin_Call { | |||
| 91 | + | return &PasskeyVerifier_BeginLogin_Call{Call: _e.mock.On("BeginLogin")} | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | func (_c *PasskeyVerifier_BeginLogin_Call) Run(run func()) *PasskeyVerifier_BeginLogin_Call { | |||
| 95 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 96 | + | run() | |||
| 97 | + | }) | |||
| 98 | + | return _c | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | func (_c *PasskeyVerifier_BeginLogin_Call) Return(options []byte, ceremony []byte, err error) *PasskeyVerifier_BeginLogin_Call { | |||
| 102 | + | _c.Call.Return(options, ceremony, err) | |||
| 103 | + | return _c | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | func (_c *PasskeyVerifier_BeginLogin_Call) RunAndReturn(run func() ([]byte, []byte, error)) *PasskeyVerifier_BeginLogin_Call { | |||
| 107 | + | _c.Call.Return(run) | |||
| 108 | + | return _c | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | // BeginRegistration provides a mock function for the type PasskeyVerifier | |||
| 112 | + | func (_mock *PasskeyVerifier) BeginRegistration(userHandle []byte, existing []domain.Passkey) ([]byte, []byte, error) { | |||
| 113 | + | ret := _mock.Called(userHandle, existing) | |||
| 114 | + | ||||
| 115 | + | if len(ret) == 0 { | |||
| 116 | + | panic("no return value specified for BeginRegistration") | |||
| 117 | + | } | |||
| 118 | + | ||||
| 119 | + | var r0 []byte | |||
| 120 | + | var r1 []byte | |||
| 121 | + | var r2 error | |||
| 122 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []domain.Passkey) ([]byte, []byte, error)); ok { | |||
| 123 | + | return returnFunc(userHandle, existing) | |||
| 124 | + | } | |||
| 125 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []domain.Passkey) []byte); ok { | |||
| 126 | + | r0 = returnFunc(userHandle, existing) | |||
| 127 | + | } else { | |||
| 128 | + | if ret.Get(0) != nil { | |||
| 129 | + | r0 = ret.Get(0).([]byte) | |||
| 130 | + | } | |||
| 131 | + | } | |||
| 132 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []domain.Passkey) []byte); ok { | |||
| 133 | + | r1 = returnFunc(userHandle, existing) | |||
| 134 | + | } else { | |||
| 135 | + | if ret.Get(1) != nil { | |||
| 136 | + | r1 = ret.Get(1).([]byte) | |||
| 137 | + | } | |||
| 138 | + | } | |||
| 139 | + | if returnFunc, ok := ret.Get(2).(func([]byte, []domain.Passkey) error); ok { | |||
| 140 | + | r2 = returnFunc(userHandle, existing) | |||
| 141 | + | } else { | |||
| 142 | + | r2 = ret.Error(2) | |||
| 143 | + | } | |||
| 144 | + | return r0, r1, r2 | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // PasskeyVerifier_BeginRegistration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginRegistration' | |||
| 148 | + | type PasskeyVerifier_BeginRegistration_Call struct { | |||
| 149 | + | *mock.Call | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | // BeginRegistration is a helper method to define mock.On call | |||
| 153 | + | // - userHandle []byte | |||
| 154 | + | // - existing []domain.Passkey | |||
| 155 | + | func (_e *PasskeyVerifier_Expecter) BeginRegistration(userHandle any, existing any) *PasskeyVerifier_BeginRegistration_Call { | |||
| 156 | + | return &PasskeyVerifier_BeginRegistration_Call{Call: _e.mock.On("BeginRegistration", userHandle, existing)} | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) Run(run func(userHandle []byte, existing []domain.Passkey)) *PasskeyVerifier_BeginRegistration_Call { | |||
| 160 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 161 | + | var arg0 []byte | |||
| 162 | + | if args[0] != nil { | |||
| 163 | + | arg0 = args[0].([]byte) | |||
| 164 | + | } | |||
| 165 | + | var arg1 []domain.Passkey | |||
| 166 | + | if args[1] != nil { | |||
| 167 | + | arg1 = args[1].([]domain.Passkey) | |||
| 168 | + | } | |||
| 169 | + | run( | |||
| 170 | + | arg0, | |||
| 171 | + | arg1, | |||
| 172 | + | ) | |||
| 173 | + | }) | |||
| 174 | + | return _c | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) Return(options []byte, ceremony []byte, err error) *PasskeyVerifier_BeginRegistration_Call { | |||
| 178 | + | _c.Call.Return(options, ceremony, err) | |||
| 179 | + | return _c | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | func (_c *PasskeyVerifier_BeginRegistration_Call) RunAndReturn(run func(userHandle []byte, existing []domain.Passkey) ([]byte, []byte, error)) *PasskeyVerifier_BeginRegistration_Call { | |||
| 183 | + | _c.Call.Return(run) | |||
| 184 | + | return _c | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | // FinishLogin provides a mock function for the type PasskeyVerifier | |||
| 188 | + | func (_mock *PasskeyVerifier) FinishLogin(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error) { | |||
| 189 | + | ret := _mock.Called(userHandle, ceremony, response, lookup) | |||
| 190 | + | ||||
| 191 | + | if len(ret) == 0 { | |||
| 192 | + | panic("no return value specified for FinishLogin") | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | var r0 domain.Passkey | |||
| 196 | + | var r1 error | |||
| 197 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) (domain.Passkey, error)); ok { | |||
| 198 | + | return returnFunc(userHandle, ceremony, response, lookup) | |||
| 199 | + | } | |||
| 200 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) domain.Passkey); ok { | |||
| 201 | + | r0 = returnFunc(userHandle, ceremony, response, lookup) | |||
| 202 | + | } else { | |||
| 203 | + | r0 = ret.Get(0).(domain.Passkey) | |||
| 204 | + | } | |||
| 205 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []byte, []byte, func(id []byte) (domain.Passkey, error)) error); ok { | |||
| 206 | + | r1 = returnFunc(userHandle, ceremony, response, lookup) | |||
| 207 | + | } else { | |||
| 208 | + | r1 = ret.Error(1) | |||
| 209 | + | } | |||
| 210 | + | return r0, r1 | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | // PasskeyVerifier_FinishLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FinishLogin' | |||
| 214 | + | type PasskeyVerifier_FinishLogin_Call struct { | |||
| 215 | + | *mock.Call | |||
| 216 | + | } | |||
| 217 | + | ||||
| 218 | + | // FinishLogin is a helper method to define mock.On call | |||
| 219 | + | // - userHandle []byte | |||
| 220 | + | // - ceremony []byte | |||
| 221 | + | // - response []byte | |||
| 222 | + | // - lookup func(id []byte) (domain.Passkey, error) | |||
| 223 | + | func (_e *PasskeyVerifier_Expecter) FinishLogin(userHandle any, ceremony any, response any, lookup any) *PasskeyVerifier_FinishLogin_Call { | |||
| 224 | + | return &PasskeyVerifier_FinishLogin_Call{Call: _e.mock.On("FinishLogin", userHandle, ceremony, response, lookup)} | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | func (_c *PasskeyVerifier_FinishLogin_Call) Run(run func(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error))) *PasskeyVerifier_FinishLogin_Call { | |||
| 228 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 229 | + | var arg0 []byte | |||
| 230 | + | if args[0] != nil { | |||
| 231 | + | arg0 = args[0].([]byte) | |||
| 232 | + | } | |||
| 233 | + | var arg1 []byte | |||
| 234 | + | if args[1] != nil { | |||
| 235 | + | arg1 = args[1].([]byte) | |||
| 236 | + | } | |||
| 237 | + | var arg2 []byte | |||
| 238 | + | if args[2] != nil { | |||
| 239 | + | arg2 = args[2].([]byte) | |||
| 240 | + | } | |||
| 241 | + | var arg3 func(id []byte) (domain.Passkey, error) | |||
| 242 | + | if args[3] != nil { | |||
| 243 | + | arg3 = args[3].(func(id []byte) (domain.Passkey, error)) | |||
| 244 | + | } | |||
| 245 | + | run( | |||
| 246 | + | arg0, | |||
| 247 | + | arg1, | |||
| 248 | + | arg2, | |||
| 249 | + | arg3, | |||
| 250 | + | ) | |||
| 251 | + | }) | |||
| 252 | + | return _c | |||
| 253 | + | } | |||
| 254 | + | ||||
| 255 | + | func (_c *PasskeyVerifier_FinishLogin_Call) Return(passkey domain.Passkey, err error) *PasskeyVerifier_FinishLogin_Call { | |||
| 256 | + | _c.Call.Return(passkey, err) | |||
| 257 | + | return _c | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | func (_c *PasskeyVerifier_FinishLogin_Call) RunAndReturn(run func(userHandle []byte, ceremony []byte, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error)) *PasskeyVerifier_FinishLogin_Call { | |||
| 261 | + | _c.Call.Return(run) | |||
| 262 | + | return _c | |||
| 263 | + | } | |||
| 264 | + | ||||
| 265 | + | // FinishRegistration provides a mock function for the type PasskeyVerifier | |||
| 266 | + | func (_mock *PasskeyVerifier) FinishRegistration(userHandle []byte, ceremony []byte, response []byte) (domain.Passkey, error) { | |||
| 267 | + | ret := _mock.Called(userHandle, ceremony, response) | |||
| 268 | + | ||||
| 269 | + | if len(ret) == 0 { | |||
| 270 | + | panic("no return value specified for FinishRegistration") | |||
| 271 | + | } | |||
| 272 | + | ||||
| 273 | + | var r0 domain.Passkey | |||
| 274 | + | var r1 error | |||
| 275 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte) (domain.Passkey, error)); ok { | |||
| 276 | + | return returnFunc(userHandle, ceremony, response) | |||
| 277 | + | } | |||
| 278 | + | if returnFunc, ok := ret.Get(0).(func([]byte, []byte, []byte) domain.Passkey); ok { | |||
| 279 | + | r0 = returnFunc(userHandle, ceremony, response) | |||
| 280 | + | } else { | |||
| 281 | + | r0 = ret.Get(0).(domain.Passkey) | |||
| 282 | + | } | |||
| 283 | + | if returnFunc, ok := ret.Get(1).(func([]byte, []byte, []byte) error); ok { | |||
| 284 | + | r1 = returnFunc(userHandle, ceremony, response) | |||
| 285 | + | } else { | |||
| 286 | + | r1 = ret.Error(1) | |||
| 287 | + | } | |||
| 288 | + | return r0, r1 | |||
| 289 | + | } | |||
| 290 | + | ||||
| 291 | + | // PasskeyVerifier_FinishRegistration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FinishRegistration' | |||
| 292 | + | type PasskeyVerifier_FinishRegistration_Call struct { | |||
| 293 | + | *mock.Call | |||
| 294 | + | } | |||
| 295 | + | ||||
| 296 | + | // FinishRegistration is a helper method to define mock.On call | |||
| 297 | + | // - userHandle []byte | |||
| 298 | + | // - ceremony []byte | |||
| 299 | + | // - response []byte | |||
| 300 | + | func (_e *PasskeyVerifier_Expecter) FinishRegistration(userHandle any, ceremony any, response any) *PasskeyVerifier_FinishRegistration_Call { | |||
| 301 | + | return &PasskeyVerifier_FinishRegistration_Call{Call: _e.mock.On("FinishRegistration", userHandle, ceremony, response)} | |||
| 302 | + | } | |||
| 303 | + | ||||
| 304 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) Run(run func(userHandle []byte, ceremony []byte, response []byte)) *PasskeyVerifier_FinishRegistration_Call { | |||
| 305 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 306 | + | var arg0 []byte | |||
| 307 | + | if args[0] != nil { | |||
| 308 | + | arg0 = args[0].([]byte) | |||
| 309 | + | } | |||
| 310 | + | var arg1 []byte | |||
| 311 | + | if args[1] != nil { | |||
| 312 | + | arg1 = args[1].([]byte) | |||
| 313 | + | } | |||
| 314 | + | var arg2 []byte | |||
| 315 | + | if args[2] != nil { | |||
| 316 | + | arg2 = args[2].([]byte) | |||
| 317 | + | } | |||
| 318 | + | run( | |||
| 319 | + | arg0, | |||
| 320 | + | arg1, | |||
| 321 | + | arg2, | |||
| 322 | + | ) | |||
| 323 | + | }) | |||
| 324 | + | return _c | |||
| 325 | + | } | |||
| 326 | + | ||||
| 327 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) Return(passkey domain.Passkey, err error) *PasskeyVerifier_FinishRegistration_Call { | |||
| 328 | + | _c.Call.Return(passkey, err) | |||
| 329 | + | return _c | |||
| 330 | + | } | |||
| 331 | + | ||||
| 332 | + | func (_c *PasskeyVerifier_FinishRegistration_Call) RunAndReturn(run func(userHandle []byte, ceremony []byte, response []byte) (domain.Passkey, error)) *PasskeyVerifier_FinishRegistration_Call { | |||
| 333 | + | _c.Call.Return(run) | |||
| 334 | + | return _c | |||
| 335 | + | } | |||
M internal/ports/ports.go +45 -0
| @@ -318,6 +318,51 @@ type SessionStore interface { | |||
| 318 | 318 | BumpEpoch(ctx context.Context) (uint64, error) | |
| 319 | 319 | } | |
| 320 | 320 | ||
| 321 | + | // PasskeyStore keeps the owner's passkeys (ADR 0026) and the WebAuthn user | |
| 322 | + | // handle they belong to. | |
| 323 | + | type PasskeyStore interface { | |
| 324 | + | // UserHandle returns the 32-byte WebAuthn user handle of the owner. The | |
| 325 | + | // first call on an empty store generates and stores it; it never changes. | |
| 326 | + | UserHandle(ctx context.Context) ([]byte, error) | |
| 327 | + | // List returns every passkey, oldest first. | |
| 328 | + | List(ctx context.Context) ([]domain.Passkey, error) | |
| 329 | + | // Get returns the passkey with that credential id, or domain.ErrNotFound. | |
| 330 | + | Get(ctx context.Context, id []byte) (domain.Passkey, error) | |
| 331 | + | // Create stores the passkey. It returns domain.ErrAlreadyExists if the | |
| 332 | + | // credential id is taken. It does not bound the number of passkeys: the | |
| 333 | + | // use case checks domain.MaxPasskeys. | |
| 334 | + | Create(ctx context.Context, p domain.Passkey) error | |
| 335 | + | // Update replaces the stored passkey (sign count, backup state, last | |
| 336 | + | // use), or returns domain.ErrNotFound. | |
| 337 | + | Update(ctx context.Context, p domain.Passkey) error | |
| 338 | + | // Delete removes the passkey, or returns domain.ErrNotFound. | |
| 339 | + | Delete(ctx context.Context, id []byte) error | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | // PasskeyVerifier runs the WebAuthn ceremonies (ADR 0026). It hides the | |
| 343 | + | // WebAuthn library: the ceremony data it hands out is opaque to the caller, | |
| 344 | + | // which keeps it between the begin and the finish of a ceremony and passes it | |
| 345 | + | // back untouched. Every verification failure is domain.ErrUnauthenticated, | |
| 346 | + | // wrapped with a reason meant for logs, not for callers. | |
| 347 | + | type PasskeyVerifier interface { | |
| 348 | + | // BeginRegistration starts the registration of a passkey for the owner. | |
| 349 | + | // options is the JSON of the credential creation options for the browser | |
| 350 | + | // ({"publicKey": {...}}); existing passkeys are excluded. | |
| 351 | + | BeginRegistration(userHandle []byte, existing []domain.Passkey) (options []byte, ceremony []byte, err error) | |
| 352 | + | // FinishRegistration checks the browser's response (the JSON of | |
| 353 | + | // PublicKeyCredential.toJSON()) and returns the new passkey, without a name | |
| 354 | + | // or creation time. | |
| 355 | + | FinishRegistration(userHandle []byte, ceremony, response []byte) (domain.Passkey, error) | |
| 356 | + | // BeginLogin starts a discoverable login. options is the JSON of the | |
| 357 | + | // credential request options for the browser ({"publicKey": {...}}). | |
| 358 | + | BeginLogin() (options []byte, ceremony []byte, err error) | |
| 359 | + | // FinishLogin checks the browser's response and returns the stored passkey | |
| 360 | + | // updated with the new sign count and backup state. lookup returns | |
| 361 | + | // domain.ErrNotFound for an unknown credential id. A counter that did not | |
| 362 | + | // advance gives domain.ErrPasskeyCloned. | |
| 363 | + | FinishLogin(userHandle []byte, ceremony, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error) | |
| 364 | + | } | |
| 365 | + | ||
| 321 | 366 | // EventBus fans events out to in-process subscribers. | |
| 322 | 367 | type EventBus interface { | |
| 323 | 368 | // Publish hands the event to every subscriber without blocking. A | |
| @@ -318,6 +318,51 @@ type SessionStore interface { | |||||
| 318 | BumpEpoch(ctx context.Context) (uint64, error) | 318 | BumpEpoch(ctx context.Context) (uint64, error) | ||
| 319 | } | 319 | } | ||
| 320 | 320 | ||||
| 321 | + | // PasskeyStore keeps the owner's passkeys (ADR 0026) and the WebAuthn user | |||
| 322 | + | // handle they belong to. | |||
| 323 | + | type PasskeyStore interface { | |||
| 324 | + | // UserHandle returns the 32-byte WebAuthn user handle of the owner. The | |||
| 325 | + | // first call on an empty store generates and stores it; it never changes. | |||
| 326 | + | UserHandle(ctx context.Context) ([]byte, error) | |||
| 327 | + | // List returns every passkey, oldest first. | |||
| 328 | + | List(ctx context.Context) ([]domain.Passkey, error) | |||
| 329 | + | // Get returns the passkey with that credential id, or domain.ErrNotFound. | |||
| 330 | + | Get(ctx context.Context, id []byte) (domain.Passkey, error) | |||
| 331 | + | // Create stores the passkey. It returns domain.ErrAlreadyExists if the | |||
| 332 | + | // credential id is taken. It does not bound the number of passkeys: the | |||
| 333 | + | // use case checks domain.MaxPasskeys. | |||
| 334 | + | Create(ctx context.Context, p domain.Passkey) error | |||
| 335 | + | // Update replaces the stored passkey (sign count, backup state, last | |||
| 336 | + | // use), or returns domain.ErrNotFound. | |||
| 337 | + | Update(ctx context.Context, p domain.Passkey) error | |||
| 338 | + | // Delete removes the passkey, or returns domain.ErrNotFound. | |||
| 339 | + | Delete(ctx context.Context, id []byte) error | |||
| 340 | + | } | |||
| 341 | + | ||||
| 342 | + | // PasskeyVerifier runs the WebAuthn ceremonies (ADR 0026). It hides the | |||
| 343 | + | // WebAuthn library: the ceremony data it hands out is opaque to the caller, | |||
| 344 | + | // which keeps it between the begin and the finish of a ceremony and passes it | |||
| 345 | + | // back untouched. Every verification failure is domain.ErrUnauthenticated, | |||
| 346 | + | // wrapped with a reason meant for logs, not for callers. | |||
| 347 | + | type PasskeyVerifier interface { | |||
| 348 | + | // BeginRegistration starts the registration of a passkey for the owner. | |||
| 349 | + | // options is the JSON of the credential creation options for the browser | |||
| 350 | + | // ({"publicKey": {...}}); existing passkeys are excluded. | |||
| 351 | + | BeginRegistration(userHandle []byte, existing []domain.Passkey) (options []byte, ceremony []byte, err error) | |||
| 352 | + | // FinishRegistration checks the browser's response (the JSON of | |||
| 353 | + | // PublicKeyCredential.toJSON()) and returns the new passkey, without a name | |||
| 354 | + | // or creation time. | |||
| 355 | + | FinishRegistration(userHandle []byte, ceremony, response []byte) (domain.Passkey, error) | |||
| 356 | + | // BeginLogin starts a discoverable login. options is the JSON of the | |||
| 357 | + | // credential request options for the browser ({"publicKey": {...}}). | |||
| 358 | + | BeginLogin() (options []byte, ceremony []byte, err error) | |||
| 359 | + | // FinishLogin checks the browser's response and returns the stored passkey | |||
| 360 | + | // updated with the new sign count and backup state. lookup returns | |||
| 361 | + | // domain.ErrNotFound for an unknown credential id. A counter that did not | |||
| 362 | + | // advance gives domain.ErrPasskeyCloned. | |||
| 363 | + | FinishLogin(userHandle []byte, ceremony, response []byte, lookup func(id []byte) (domain.Passkey, error)) (domain.Passkey, error) | |||
| 364 | + | } | |||
| 365 | + | ||||
| 321 | // EventBus fans events out to in-process subscribers. | 366 | // EventBus fans events out to in-process subscribers. | ||
| 322 | type EventBus interface { | 367 | type EventBus interface { | ||
| 323 | // Publish hands the event to every subscriber without blocking. A | 368 | // Publish hands the event to every subscriber without blocking. A | ||
M proto/ermite/storage/session.proto +31 -0
| @@ -6,6 +6,8 @@ syntax = "proto3"; | |||
| 6 | 6 | ||
| 7 | 7 | package ermite.storage; | |
| 8 | 8 | ||
| 9 | + | import "google/protobuf/timestamp.proto"; | |
| 10 | + | ||
| 9 | 11 | option go_package = "bnema.dev/ermite/gen/ermite/storage;storagepb"; | |
| 10 | 12 | ||
| 11 | 13 | // SessionKey is stored under the key "session/key": the HS256 secret that | |
| @@ -22,3 +24,32 @@ message SessionKey { | |||
| 22 | 24 | message SessionEpoch { | |
| 23 | 25 | uint64 epoch = 1; | |
| 24 | 26 | } | |
| 27 | + | ||
| 28 | + | // PasskeyUser is stored under the key "passkey/user": the WebAuthn user | |
| 29 | + | // handle of the owner, generated on first use. It identifies the owner to | |
| 30 | + | // authenticators and never changes. | |
| 31 | + | message PasskeyUser { | |
| 32 | + | // 32 random bytes. | |
| 33 | + | bytes handle = 1; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // Passkey is stored under the key "passkey/cred/<base64url credential id>": | |
| 37 | + | // a WebAuthn credential that can sign the owner in (ADR 0026). It holds the | |
| 38 | + | // public key only. | |
| 39 | + | message Passkey { | |
| 40 | + | // Credential id, at most 1023 bytes. | |
| 41 | + | bytes id = 1; | |
| 42 | + | // Name given by the owner. | |
| 43 | + | string name = 2; | |
| 44 | + | // COSE public key. | |
| 45 | + | bytes public_key = 3; | |
| 46 | + | string attestation_type = 4; | |
| 47 | + | repeated string transports = 5; | |
| 48 | + | // Last signature counter; 0 for synced passkeys. | |
| 49 | + | uint32 sign_count = 6; | |
| 50 | + | bool backup_eligible = 7; | |
| 51 | + | bool backup_state = 8; | |
| 52 | + | google.protobuf.Timestamp created_at = 9; | |
| 53 | + | // Unset until the passkey is first used. | |
| 54 | + | google.protobuf.Timestamp last_used_at = 10; | |
| 55 | + | } | |
| @@ -6,6 +6,8 @@ syntax = "proto3"; | |||||
| 6 | 6 | ||||
| 7 | package ermite.storage; | 7 | package ermite.storage; | ||
| 8 | 8 | ||||
| 9 | + | import "google/protobuf/timestamp.proto"; | |||
| 10 | + | ||||
| 9 | option go_package = "bnema.dev/ermite/gen/ermite/storage;storagepb"; | 11 | option go_package = "bnema.dev/ermite/gen/ermite/storage;storagepb"; | ||
| 10 | 12 | ||||
| 11 | // SessionKey is stored under the key "session/key": the HS256 secret that | 13 | // SessionKey is stored under the key "session/key": the HS256 secret that | ||
| @@ -22,3 +24,32 @@ message SessionKey { | |||||
| 22 | message SessionEpoch { | 24 | message SessionEpoch { | ||
| 23 | uint64 epoch = 1; | 25 | uint64 epoch = 1; | ||
| 24 | } | 26 | } | ||
| 27 | + | ||||
| 28 | + | // PasskeyUser is stored under the key "passkey/user": the WebAuthn user | |||
| 29 | + | // handle of the owner, generated on first use. It identifies the owner to | |||
| 30 | + | // authenticators and never changes. | |||
| 31 | + | message PasskeyUser { | |||
| 32 | + | // 32 random bytes. | |||
| 33 | + | bytes handle = 1; | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // Passkey is stored under the key "passkey/cred/<base64url credential id>": | |||
| 37 | + | // a WebAuthn credential that can sign the owner in (ADR 0026). It holds the | |||
| 38 | + | // public key only. | |||
| 39 | + | message Passkey { | |||
| 40 | + | // Credential id, at most 1023 bytes. | |||
| 41 | + | bytes id = 1; | |||
| 42 | + | // Name given by the owner. | |||
| 43 | + | string name = 2; | |||
| 44 | + | // COSE public key. | |||
| 45 | + | bytes public_key = 3; | |||
| 46 | + | string attestation_type = 4; | |||
| 47 | + | repeated string transports = 5; | |||
| 48 | + | // Last signature counter; 0 for synced passkeys. | |||
| 49 | + | uint32 sign_count = 6; | |||
| 50 | + | bool backup_eligible = 7; | |||
| 51 | + | bool backup_state = 8; | |||
| 52 | + | google.protobuf.Timestamp created_at = 9; | |||
| 53 | + | // Unset until the passkey is first used. | |||
| 54 | + | google.protobuf.Timestamp last_used_at = 10; | |||
| 55 | + | } | |||