v0.18.1 e08f000

oidc.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package kvstore
4
5import (
6	"context"
7	"fmt"
8	"slices"
9	"strings"
10
11	kv "bnema.dev/ermite-kv"
12	"google.golang.org/protobuf/proto"
13
14	storagepb "bnema.dev/ermite/gen/ermite/storage"
15	"bnema.dev/ermite/internal/domain"
16	"bnema.dev/ermite/internal/ports"
17)
18
19// Key layout of the signing keys of the OIDC ID tokens (CI-4):
20//
21//	oidc/key/<kid>    OIDCKey (the private key sealed, see secretbox)
22const oidcKeyPrefix = "oidc/key/"
23
24var _ ports.OIDCKeyStore = (*Store)(nil)
25
26// ListOIDCKeys returns every stored key, oldest first.
27func (s *Store) ListOIDCKeys(ctx context.Context) ([]domain.OIDCKey, error) {
28	var out []domain.OIDCKey
29	for key, item := range s.db.Snapshot(ctx).Prefix(ctx, []byte(oidcKeyPrefix)) {
30		var pb storagepb.OIDCKey
31		if err := proto.Unmarshal(item.Value, &pb); err != nil {
32			return nil, fmt.Errorf("decode oidc key %s: %w", key, err)
33		}
34		out = append(out, domain.OIDCKey{
35			ID: strings.TrimPrefix(string(key), oidcKeyPrefix), Sealed: pb.GetSealed(),
36			CreatedAt: decodeTime(pb.GetCreatedAt()), RetiredAt: decodeTime(pb.GetRetiredAt()),
37		})
38	}
39	slices.SortStableFunc(out, func(a, b domain.OIDCKey) int { return a.CreatedAt.Compare(b.CreatedAt) })
40	return out, nil
41}
42
43// SaveOIDCKeys writes the keys of put (created or replaced) and deletes those
44// of drop, in one batch.
45func (s *Store) SaveOIDCKeys(ctx context.Context, put []domain.OIDCKey, drop []string) error {
46	var b kv.Batch
47	for _, k := range put {
48		if k.ID == "" || strings.Contains(k.ID, "/") {
49			return fmt.Errorf("%w: oidc key id %q", domain.ErrInvalid, k.ID)
50		}
51		b.Put([]byte(domain.OIDCKeyPath(k.ID)), mustMarshal(&storagepb.OIDCKey{
52			Sealed: k.Sealed, CreatedAt: encodeTime(k.CreatedAt), RetiredAt: encodeTime(k.RetiredAt),
53		}))
54	}
55	for _, id := range drop {
56		b.Delete([]byte(domain.OIDCKeyPath(id)))
57	}
58	if _, err := s.db.Apply(ctx, &b); err != nil {
59		return fmt.Errorf("store the oidc keys: %w", err)
60	}
61	return nil
62}
63
64// RawOIDCKey returns the stored value of the key, as is. Tests use it to check
65// that no private key is stored in the clear.
66func (s *Store) RawOIDCKey(ctx context.Context, kid string) ([]byte, bool) {
67	item, ok := s.db.Get(ctx, []byte(domain.OIDCKeyPath(kid)))
68	if !ok {
69		return nil, false
70	}
71	return item.Value, true
72}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close