mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-05-05 03:48:14 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04b8e9884b | |||
| 0244f39387 | |||
| 1d0a4627a9 | |||
| 956d2f55c3 | |||
| 5e822d99e1 | |||
| 373ee8806e | |||
| a14d64c8ba | |||
| d51e3efe32 | |||
| d73cc628fb | |||
| a8737ab0bd | |||
| 11793c9869 |
@@ -26,6 +26,12 @@ jobs:
|
|||||||
- name: Go dependencies
|
- name: Go dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Check codegen is up to date
|
||||||
|
run: |
|
||||||
|
go generate ./internal/repository/...
|
||||||
|
git diff --exit-code -- internal/repository/
|
||||||
|
git status --porcelain -- internal/repository/ | grep -q . && echo "untracked files in internal/repository/" && exit 1 || true
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
|
|||||||
Vendored
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Connect to server",
|
|
||||||
"type": "go",
|
|
||||||
"request": "attach",
|
|
||||||
"mode": "remote",
|
|
||||||
"remotePath": "/tinyauth",
|
|
||||||
"port": 4000,
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"debugAdapter": "legacy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-c
|
|||||||
|
|
||||||
# Deps
|
# Deps
|
||||||
deps:
|
deps:
|
||||||
bun install --cwd frontend
|
bun install --frozen-lockfile --cwd frontend
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
# Clean data
|
# Clean data
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<a href="https://scorecard.dev/viewer/?uri=github.com/tinyauthapp/tinyauth" target="_blank" title="OpenSSF Scorecard">
|
<a href="https://scorecard.dev/viewer/?uri=github.com/tinyauthapp/tinyauth" target="_blank" title="OpenSSF Scorecard">
|
||||||
<img src="https://api.scorecard.dev/projects/github.com/tinyauthapp/tinyauth/badge">
|
<img src="https://api.scorecard.dev/projects/github.com/tinyauthapp/tinyauth/badge">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://www.bestpractices.dev/projects/12681" target="_blank" title="OSSF Best Practices"><img src="https://www.bestpractices.dev/projects/12681/baseline"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -0,0 +1,540 @@
|
|||||||
|
// gen/sqlc-wrapper generates store.go wrapper files for each sqlc driver package under
|
||||||
|
// internal/repository/<driver>/. Run via:
|
||||||
|
//
|
||||||
|
// go generate ./internal/repository/...
|
||||||
|
//
|
||||||
|
// The generator introspects *Queries methods and the model/params types in the
|
||||||
|
// driver package, then emits a store.go that wraps *Queries so it satisfies
|
||||||
|
// repository.Store using the canonical shared types in the parent package.
|
||||||
|
// This generator is specific to sqlc-generated drivers. Non-sqlc drivers should
|
||||||
|
// implement repository.Store directly by hand.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
driverPkg := flag.String("pkg", "", "import path of the driver package")
|
||||||
|
out := flag.String("out", "store.go", "output filename relative to driver package directory")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *driverPkg == "" {
|
||||||
|
log.Fatal("-pkg is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the driver package directory so we can overlay the output file
|
||||||
|
// with a valid stub. This prevents a stale store.go from poisoning the
|
||||||
|
// type-checker and producing cryptic "undefined" errors.
|
||||||
|
driverDir, err := pkgDir(*driverPkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("resolve driver dir: %v", err)
|
||||||
|
}
|
||||||
|
outPath := filepath.Join(driverDir, *out)
|
||||||
|
if filepath.IsAbs(*out) {
|
||||||
|
outPath = *out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub replaces the output file during load so stale generated code is ignored.
|
||||||
|
stub := []byte("package " + filepath.Base(driverDir) + "\n")
|
||||||
|
|
||||||
|
cfg := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports,
|
||||||
|
Overlay: map[string][]byte{outPath: stub},
|
||||||
|
}
|
||||||
|
pkgs, err := packages.Load(cfg, *driverPkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("load %s: %v", *driverPkg, err)
|
||||||
|
}
|
||||||
|
if len(pkgs) != 1 {
|
||||||
|
log.Fatalf("expected 1 package, got %d", len(pkgs))
|
||||||
|
}
|
||||||
|
pkg := pkgs[0]
|
||||||
|
if len(pkg.Errors) > 0 {
|
||||||
|
for _, e := range pkg.Errors {
|
||||||
|
log.Printf("package error: %v", e)
|
||||||
|
}
|
||||||
|
log.Fatal("package has errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoPkg := parentPkg(*driverPkg)
|
||||||
|
|
||||||
|
// Load the parent (repository) package so we can validate struct shapes.
|
||||||
|
repoPkgs, err := packages.Load(cfg, repoPkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("load repo pkg %s: %v", repoPkg, err)
|
||||||
|
}
|
||||||
|
if len(repoPkgs) != 1 || len(repoPkgs[0].Errors) > 0 {
|
||||||
|
log.Fatalf("could not load repo package %s cleanly", repoPkg)
|
||||||
|
}
|
||||||
|
if err := validateStructShapes(pkg.Types, repoPkgs[0].Types); err != nil {
|
||||||
|
log.Fatalf("struct shape mismatch: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check *Queries covers every method in repository.Store before generating.
|
||||||
|
if err := validateStoreCoverage(pkg.Types, repoPkgs[0].Types); err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
methods, err := collectMethods(pkg.Types)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
models, _ := collectTypes(pkg.Types)
|
||||||
|
|
||||||
|
data := tmplData{
|
||||||
|
PkgName: pkg.Name,
|
||||||
|
RepoPkg: repoPkg,
|
||||||
|
ModelTypes: models,
|
||||||
|
Methods: renderMethods(methods),
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := render(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("render: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(outPath, src, 0644); err != nil {
|
||||||
|
log.Fatalf("write %s: %v", outPath, err)
|
||||||
|
}
|
||||||
|
fmt.Printf("wrote %s\n", outPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parentPkg(imp string) string {
|
||||||
|
parts := strings.Split(imp, "/")
|
||||||
|
return strings.Join(parts[:len(parts)-1], "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkgDir returns the on-disk directory for an import path using `go list`.
|
||||||
|
func pkgDir(importPath string) (string, error) {
|
||||||
|
out, err := exec.Command("go", "list", "-f", "{{.Dir}}", importPath).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("go list %s: %w", importPath, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStoreCoverage checks that every method declared in repository.Store
|
||||||
|
// exists on *Queries in the driver package. Missing methods are reported by
|
||||||
|
// name so the developer knows exactly which SQL queries need to be added.
|
||||||
|
func validateStoreCoverage(driverPkg, repoPkg *types.Package) error {
|
||||||
|
// Collect *Queries method names.
|
||||||
|
queriesObj := driverPkg.Scope().Lookup("Queries")
|
||||||
|
if queriesObj == nil {
|
||||||
|
return fmt.Errorf("Queries type not found in driver package")
|
||||||
|
}
|
||||||
|
queriesNamed := queriesObj.Type().(*types.Named)
|
||||||
|
queriesMS := types.NewMethodSet(types.NewPointer(queriesNamed))
|
||||||
|
queriesMethods := make(map[string]bool)
|
||||||
|
for m := range queriesMS.Methods() {
|
||||||
|
queriesMethods[m.Obj().Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect repository.Store interface methods.
|
||||||
|
storeObj := repoPkg.Scope().Lookup("Store")
|
||||||
|
if storeObj == nil {
|
||||||
|
return fmt.Errorf("Store type not found in repository package")
|
||||||
|
}
|
||||||
|
storeIface, ok := storeObj.Type().Underlying().(*types.Interface)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("repository.Store is not an interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
var missing []string
|
||||||
|
for i := range storeIface.NumMethods() {
|
||||||
|
name := storeIface.Method(i).Name()
|
||||||
|
if !queriesMethods[name] {
|
||||||
|
missing = append(missing, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missing) > 0 {
|
||||||
|
sort.Strings(missing)
|
||||||
|
return fmt.Errorf(
|
||||||
|
"driver *Queries is missing %d method(s) required by repository.Store:\n - %s\n\nRun sqlc generate to regenerate query methods, or add the missing SQL queries.",
|
||||||
|
len(missing), strings.Join(missing, "\n - "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodInfo struct {
|
||||||
|
Name string
|
||||||
|
Params []paramInfo
|
||||||
|
Results []resultInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramInfo struct {
|
||||||
|
Name string
|
||||||
|
TypeStr string // local (unqualified) type name
|
||||||
|
RepoType string // "repository.X" if this is a driver model/params type; else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type resultInfo struct {
|
||||||
|
TypeStr string
|
||||||
|
IsSlice bool
|
||||||
|
RepoType string // "repository.X" if driver type; else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectMethods(pkg *types.Package) ([]methodInfo, error) {
|
||||||
|
obj := pkg.Scope().Lookup("Queries")
|
||||||
|
if obj == nil {
|
||||||
|
return nil, fmt.Errorf("queries type not found in %s", pkg.Path())
|
||||||
|
}
|
||||||
|
named, ok := obj.Type().(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("queries is not a named type")
|
||||||
|
}
|
||||||
|
ms := types.NewMethodSet(types.NewPointer(named))
|
||||||
|
|
||||||
|
var out []methodInfo
|
||||||
|
for method := range ms.Methods() {
|
||||||
|
fn, ok := method.Obj().(*types.Func)
|
||||||
|
if !ok || fn.Name() == "WithTx" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sig := fn.Type().(*types.Signature)
|
||||||
|
mi := methodInfo{Name: fn.Name()}
|
||||||
|
|
||||||
|
// params: skip receiver + first (context.Context)
|
||||||
|
for i := 1; i < sig.Params().Len(); i++ {
|
||||||
|
p := sig.Params().At(i)
|
||||||
|
mi.Params = append(mi.Params, makeParam(p.Name(), p.Type(), pkg.Path()))
|
||||||
|
}
|
||||||
|
// results: skip error
|
||||||
|
for r := range sig.Results().Variables() {
|
||||||
|
if r.Type().String() == "error" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mi.Results = append(mi.Results, makeResult(r.Type(), pkg.Path()))
|
||||||
|
}
|
||||||
|
out = append(out, mi)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeParam(name string, t types.Type, driverPath string) paramInfo {
|
||||||
|
pi := paramInfo{Name: name}
|
||||||
|
pi.TypeStr = localName(t, driverPath)
|
||||||
|
pi.RepoType = repoName(t, driverPath)
|
||||||
|
return pi
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeResult(t types.Type, driverPath string) resultInfo {
|
||||||
|
ri := resultInfo{}
|
||||||
|
if sl, ok := t.(*types.Slice); ok {
|
||||||
|
ri.IsSlice = true
|
||||||
|
t = sl.Elem()
|
||||||
|
}
|
||||||
|
ri.TypeStr = localName(t, driverPath)
|
||||||
|
ri.RepoType = repoName(t, driverPath)
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
func localName(t types.Type, driverPath string) string {
|
||||||
|
named, ok := t.(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
return types.TypeString(t, nil)
|
||||||
|
}
|
||||||
|
if named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == driverPath {
|
||||||
|
return named.Obj().Name()
|
||||||
|
}
|
||||||
|
return types.TypeString(t, func(p *types.Package) string { return p.Name() })
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoName(t types.Type, driverPath string) string {
|
||||||
|
named, ok := t.(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == driverPath {
|
||||||
|
return "repository." + named.Obj().Name()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectTypes(pkg *types.Package) (models []string, params []string) {
|
||||||
|
for _, name := range pkg.Scope().Names() {
|
||||||
|
obj := pkg.Scope().Lookup(name)
|
||||||
|
if obj == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tn, ok := obj.(*types.TypeName)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
named, ok := tn.Type().(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := named.Underlying().(*types.Struct); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "Queries", "DBTX", "Store":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(name, "Params") {
|
||||||
|
params = append(params, name)
|
||||||
|
} else {
|
||||||
|
models = append(models, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStructShapes checks that every model/params struct in the driver
|
||||||
|
// package has fields that exactly match the corresponding type in the repo
|
||||||
|
// (parent) package. This catches drift between sqlc-generated types and the
|
||||||
|
// canonical repository types before a broken cast reaches the compiler.
|
||||||
|
func validateStructShapes(driverPkg, repoPkg *types.Package) error {
|
||||||
|
var errs []string
|
||||||
|
for _, name := range driverPkg.Scope().Names() {
|
||||||
|
obj := driverPkg.Scope().Lookup(name)
|
||||||
|
if obj == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tn, ok := obj.(*types.TypeName)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
named, ok := tn.Type().(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
driverStruct, ok := named.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "Queries", "DBTX", "Store":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repoObj := repoPkg.Scope().Lookup(name)
|
||||||
|
if repoObj == nil {
|
||||||
|
// Driver has a type not in repo — that's fine (e.g. internal helpers).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
repoNamed, ok := repoObj.Type().(*types.Named)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
repoStruct, ok := repoNamed.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: repo type is not a struct", name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareStructs(name, driverStruct, repoStruct); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(errs, "\n "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareStructs(name string, driver, repo *types.Struct) error {
|
||||||
|
if driver.NumFields() != repo.NumFields() {
|
||||||
|
return fmt.Errorf("%s: field count mismatch (driver=%d, repo=%d)",
|
||||||
|
name, driver.NumFields(), repo.NumFields())
|
||||||
|
}
|
||||||
|
for i := range driver.NumFields() {
|
||||||
|
df := driver.Field(i)
|
||||||
|
rf := repo.Field(i)
|
||||||
|
if df.Name() != rf.Name() {
|
||||||
|
return fmt.Errorf("%s: field %d name mismatch (driver=%q, repo=%q)",
|
||||||
|
name, i, df.Name(), rf.Name())
|
||||||
|
}
|
||||||
|
if !types.Identical(df.Type(), rf.Type()) {
|
||||||
|
return fmt.Errorf("%s.%s: type mismatch (driver=%s, repo=%s)",
|
||||||
|
name, df.Name(), df.Type(), rf.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// converterFn: "Session" -> "sessionToRepo"
|
||||||
|
func converterFn(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
r := []rune(s)
|
||||||
|
r[0] = []rune(strings.ToLower(string(r[0])))[0]
|
||||||
|
return string(r) + "ToRepo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderedMethod is the pre-built method body passed to the template.
|
||||||
|
type renderedMethod struct {
|
||||||
|
Signature string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderMethods converts []methodInfo into fully pre-rendered signature+body strings.
|
||||||
|
func renderMethods(methods []methodInfo) []renderedMethod {
|
||||||
|
var out []renderedMethod
|
||||||
|
for _, m := range methods {
|
||||||
|
out = append(out, renderedMethod{
|
||||||
|
Signature: buildSig(m),
|
||||||
|
Body: buildBody(m),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSig(m methodInfo) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("func (s *Store) ")
|
||||||
|
sb.WriteString(m.Name)
|
||||||
|
sb.WriteString("(ctx context.Context")
|
||||||
|
for _, p := range m.Params {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
sb.WriteString(p.Name)
|
||||||
|
sb.WriteString(" ")
|
||||||
|
if p.RepoType != "" {
|
||||||
|
sb.WriteString(p.RepoType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(p.TypeStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(") (")
|
||||||
|
for _, r := range m.Results {
|
||||||
|
if r.IsSlice {
|
||||||
|
sb.WriteString("[]")
|
||||||
|
}
|
||||||
|
if r.RepoType != "" {
|
||||||
|
sb.WriteString(r.RepoType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(r.TypeStr)
|
||||||
|
}
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString("error)")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func callArgs(m methodInfo) string {
|
||||||
|
var args []string
|
||||||
|
for _, p := range m.Params {
|
||||||
|
if p.RepoType != "" {
|
||||||
|
// convert repo type → driver type: DriverType(arg)
|
||||||
|
args = append(args, p.TypeStr+"("+p.Name+")")
|
||||||
|
} else {
|
||||||
|
args = append(args, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
return "ctx"
|
||||||
|
}
|
||||||
|
return "ctx, " + strings.Join(args, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBody(m methodInfo) string {
|
||||||
|
call := "s.q." + m.Name + "(" + callArgs(m) + ")"
|
||||||
|
|
||||||
|
// no repo-typed result → direct return
|
||||||
|
if len(m.Results) == 0 || m.Results[0].RepoType == "" {
|
||||||
|
return "\treturn mapErr(" + call + ")\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
r := m.Results[0]
|
||||||
|
if r.IsSlice {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"\trows, err := %s\n\tif err != nil {\n\t\treturn nil, mapErr(err)\n\t}\n\tout := make([]%s, len(rows))\n\tfor i, row := range rows {\n\t\tout[i] = %s(row)\n\t}\n\treturn out, nil\n",
|
||||||
|
call, r.RepoType, converterFn(r.TypeStr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"\tr, err := %s\n\tif err != nil {\n\t\treturn %s{}, mapErr(err)\n\t}\n\treturn %s(r), nil\n",
|
||||||
|
call, r.RepoType, converterFn(r.TypeStr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tmplData struct {
|
||||||
|
PkgName string
|
||||||
|
RepoPkg string
|
||||||
|
ModelTypes []string
|
||||||
|
Methods []renderedMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeSrc = `// Code generated by cmd/gen/sqlc-wrapper. DO NOT EDIT.
|
||||||
|
package {{.PkgName}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"{{.RepoPkg}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store wraps *Queries and implements repository.Store.
|
||||||
|
type Store struct {
|
||||||
|
q *Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore wraps a *Queries to satisfy repository.Store.
|
||||||
|
func NewStore(q *Queries) repository.Store {
|
||||||
|
return &Store{q: q}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMap = []struct {
|
||||||
|
from error
|
||||||
|
to error
|
||||||
|
}{
|
||||||
|
{sql.ErrNoRows, repository.ErrNotFound},
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapErr(err error) error {
|
||||||
|
for _, e := range errMap {
|
||||||
|
if errors.Is(err, e.from) {
|
||||||
|
return e.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .ModelTypes -}}
|
||||||
|
func {{converterFn .}}(v {{.}}) repository.{{.}} {
|
||||||
|
return repository.{{.}}(v)
|
||||||
|
}
|
||||||
|
{{end -}}
|
||||||
|
{{range .Methods}}{{.Signature}} {
|
||||||
|
{{.Body}}}
|
||||||
|
|
||||||
|
{{end}}`
|
||||||
|
|
||||||
|
func render(data tmplData) ([]byte, error) {
|
||||||
|
t, err := template.New("store").Funcs(template.FuncMap{
|
||||||
|
"converterFn": converterFn,
|
||||||
|
}).Parse(storeSrc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.Execute(&buf, data); err != nil {
|
||||||
|
return nil, fmt.Errorf("execute template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return buf.Bytes(), fmt.Errorf("format source: %w\nraw:\n%s", err, buf.String())
|
||||||
|
}
|
||||||
|
return formatted, nil
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ WORKDIR /frontend
|
|||||||
COPY ./frontend/package.json ./
|
COPY ./frontend/package.json ./
|
||||||
COPY ./frontend/bun.lock ./
|
COPY ./frontend/bun.lock ./
|
||||||
|
|
||||||
RUN bun install
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
COPY ./frontend/public ./public
|
COPY ./frontend/public ./public
|
||||||
COPY ./frontend/src ./src
|
COPY ./frontend/src ./src
|
||||||
@@ -19,4 +19,4 @@ COPY ./frontend/vite.config.ts ./
|
|||||||
|
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
|
|
||||||
ENTRYPOINT ["bun", "run", "dev"]
|
ENTRYPOINT ["bun", "run", "dev"]
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ require (
|
|||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||||
github.com/weppos/publicsuffix-go v0.50.3
|
github.com/weppos/publicsuffix-go v0.50.3
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
|
golang.org/x/tools v0.43.0
|
||||||
gotest.tools/v3 v3.5.2
|
gotest.tools/v3 v3.5.2
|
||||||
|
k8s.io/apimachinery v0.32.2
|
||||||
|
k8s.io/client-go v0.32.2
|
||||||
modernc.org/sqlite v1.49.1
|
modernc.org/sqlite v1.49.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ require (
|
|||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
@@ -73,7 +76,9 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
@@ -92,6 +97,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
@@ -106,6 +112,7 @@ require (
|
|||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
@@ -117,15 +124,24 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||||
golang.org/x/arch v0.22.0 // indirect
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.52.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/term v0.42.0 // indirect
|
golang.org/x/term v0.42.0 // indirect
|
||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
golang.org/x/time v0.12.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -97,10 +97,14 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
|||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
@@ -118,6 +122,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@@ -130,14 +140,23 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
|||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
|
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -162,8 +181,12 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
|||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -176,6 +199,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -209,6 +234,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
@@ -242,6 +269,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
|||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -261,8 +290,12 @@ github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY
|
|||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
||||||
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
@@ -289,29 +322,54 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
|||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||||
@@ -324,11 +382,27 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
|
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
|
||||||
|
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
|
||||||
|
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
|
||||||
|
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||||
|
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
|
||||||
|
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
|
||||||
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||||
@@ -359,3 +433,9 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||||
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ var FrontendAssets embed.FS
|
|||||||
|
|
||||||
// Migrations
|
// Migrations
|
||||||
//
|
//
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations/sqlite/*.sql
|
||||||
var Migrations embed.FS
|
var Migrations embed.FS
|
||||||
|
|||||||
@@ -130,17 +130,14 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
tlog.App.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
tlog.App.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
db, err := app.SetupDatabase(app.config.Database.Path)
|
store, err := app.SetupStore()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup database: %w", err)
|
return fmt.Errorf("failed to setup database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queries
|
|
||||||
queries := repository.New(db)
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
services, err := app.initServices(queries)
|
services, err := app.initServices(store)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize services: %w", err)
|
return fmt.Errorf("failed to initialize services: %w", err)
|
||||||
@@ -196,7 +193,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
// Start db cleanup routine
|
// Start db cleanup routine
|
||||||
tlog.App.Debug().Msg("Starting database cleanup routine")
|
tlog.App.Debug().Msg("Starting database cleanup routine")
|
||||||
go app.dbCleanupRoutine(queries)
|
go app.dbCleanupRoutine(store)
|
||||||
|
|
||||||
// If analytics are not disabled, start heartbeat
|
// If analytics are not disabled, start heartbeat
|
||||||
if app.config.Analytics.Enabled {
|
if app.config.Analytics.Enabled {
|
||||||
@@ -286,7 +283,7 @@ func (app *BootstrapApp) heartbeatRoutine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) dbCleanupRoutine(queries *repository.Queries) {
|
func (app *BootstrapApp) dbCleanupRoutine(queries repository.Store) {
|
||||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/assets"
|
"github.com/tinyauthapp/tinyauth/internal/assets"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository/sqlite"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
@@ -14,7 +17,18 @@ import (
|
|||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *BootstrapApp) SetupDatabase(databasePath string) (*sql.DB, error) {
|
func (app *BootstrapApp) SetupStore() (repository.Store, error) {
|
||||||
|
switch app.config.Database.Driver {
|
||||||
|
case "memory":
|
||||||
|
return memory.New(), nil
|
||||||
|
case "sqlite", "":
|
||||||
|
return app.setupSQLite(app.config.Database.Path)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown database driver %q: valid values are sqlite, memory", app.config.Database.Driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) setupSQLite(databasePath string) (repository.Store, error) {
|
||||||
dir := filepath.Dir(databasePath)
|
dir := filepath.Dir(databasePath)
|
||||||
|
|
||||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
@@ -31,7 +45,7 @@ func (app *BootstrapApp) SetupDatabase(databasePath string) (*sql.DB, error) {
|
|||||||
// if the sqlite connection starts being a bottleneck
|
// if the sqlite connection starts being a bottleneck
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
migrations, err := iofs.New(assets.Migrations, "migrations")
|
migrations, err := iofs.New(assets.Migrations, "migrations/sqlite")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create migrations: %w", err)
|
return nil, fmt.Errorf("failed to create migrations: %w", err)
|
||||||
@@ -53,5 +67,5 @@ func (app *BootstrapApp) SetupDatabase(databasePath string) (*sql.DB, error) {
|
|||||||
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return db, nil
|
return sqlite.NewStore(sqlite.New(db)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
@@ -10,12 +12,13 @@ type Services struct {
|
|||||||
accessControlService *service.AccessControlsService
|
accessControlService *service.AccessControlsService
|
||||||
authService *service.AuthService
|
authService *service.AuthService
|
||||||
dockerService *service.DockerService
|
dockerService *service.DockerService
|
||||||
|
kubernetesService *service.KubernetesService
|
||||||
ldapService *service.LdapService
|
ldapService *service.LdapService
|
||||||
oauthBrokerService *service.OAuthBrokerService
|
oauthBrokerService *service.OAuthBrokerService
|
||||||
oidcService *service.OIDCService
|
oidcService *service.OIDCService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, error) {
|
func (app *BootstrapApp) initServices(queries repository.Store) (Services, error) {
|
||||||
services := Services{}
|
services := Services{}
|
||||||
|
|
||||||
ldapService := service.NewLdapService(service.LdapServiceConfig{
|
ldapService := service.NewLdapService(service.LdapServiceConfig{
|
||||||
@@ -38,17 +41,34 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
|
|||||||
|
|
||||||
services.ldapService = ldapService
|
services.ldapService = ldapService
|
||||||
|
|
||||||
dockerService := service.NewDockerService()
|
var labelProvider service.LabelProvider
|
||||||
|
var dockerService *service.DockerService
|
||||||
|
var kubernetesService *service.KubernetesService
|
||||||
|
|
||||||
err = dockerService.Init()
|
useKubernetes := app.config.LabelProvider == "kubernetes" ||
|
||||||
|
(app.config.LabelProvider == "auto" && os.Getenv("KUBERNETES_SERVICE_HOST") != "")
|
||||||
|
|
||||||
if err != nil {
|
if useKubernetes {
|
||||||
return Services{}, err
|
tlog.App.Debug().Msg("Using Kubernetes label provider")
|
||||||
|
kubernetesService = service.NewKubernetesService()
|
||||||
|
err = kubernetesService.Init()
|
||||||
|
if err != nil {
|
||||||
|
return Services{}, err
|
||||||
|
}
|
||||||
|
services.kubernetesService = kubernetesService
|
||||||
|
labelProvider = kubernetesService
|
||||||
|
} else {
|
||||||
|
tlog.App.Debug().Msg("Using Docker label provider")
|
||||||
|
dockerService = service.NewDockerService()
|
||||||
|
err = dockerService.Init()
|
||||||
|
if err != nil {
|
||||||
|
return Services{}, err
|
||||||
|
}
|
||||||
|
services.dockerService = dockerService
|
||||||
|
labelProvider = dockerService
|
||||||
}
|
}
|
||||||
|
|
||||||
services.dockerService = dockerService
|
accessControlsService := service.NewAccessControlsService(labelProvider, app.config.Apps)
|
||||||
|
|
||||||
accessControlsService := service.NewAccessControlsService(dockerService, app.config.Apps)
|
|
||||||
|
|
||||||
err = accessControlsService.Init()
|
err = accessControlsService.Init()
|
||||||
|
|
||||||
@@ -80,7 +100,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
|
|||||||
SessionCookieName: app.context.sessionCookieName,
|
SessionCookieName: app.context.sessionCookieName,
|
||||||
IP: app.config.Auth.IP,
|
IP: app.config.Auth.IP,
|
||||||
LDAPGroupsCacheTTL: app.config.Ldap.GroupCacheTTL,
|
LDAPGroupsCacheTTL: app.config.Ldap.GroupCacheTTL,
|
||||||
}, dockerService, services.ldapService, queries, services.oauthBrokerService)
|
}, services.ldapService, queries, services.oauthBrokerService)
|
||||||
|
|
||||||
err = authService.Init()
|
err = authService.Init()
|
||||||
|
|
||||||
|
|||||||
+19
-16
@@ -4,7 +4,8 @@ package config
|
|||||||
func NewDefaultConfiguration() *Config {
|
func NewDefaultConfiguration() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Database: DatabaseConfig{
|
Database: DatabaseConfig{
|
||||||
Path: "./tinyauth.db",
|
Driver: "sqlite",
|
||||||
|
Path: "./tinyauth.db",
|
||||||
},
|
},
|
||||||
Analytics: AnalyticsConfig{
|
Analytics: AnalyticsConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@@ -59,6 +60,7 @@ func NewDefaultConfiguration() *Config {
|
|||||||
Experimental: ExperimentalConfig{
|
Experimental: ExperimentalConfig{
|
||||||
ConfigFile: "",
|
ConfigFile: "",
|
||||||
},
|
},
|
||||||
|
LabelProvider: "auto",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,25 +78,26 @@ var RedirectCookieName = "tinyauth-redirect"
|
|||||||
var OAuthSessionCookieName = "tinyauth-oauth"
|
var OAuthSessionCookieName = "tinyauth-oauth"
|
||||||
|
|
||||||
// Main app config
|
// Main app config
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
||||||
Database DatabaseConfig `description:"Database configuration." yaml:"database"`
|
Database DatabaseConfig `description:"Database configuration." yaml:"database"`
|
||||||
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"`
|
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"`
|
||||||
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"`
|
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"`
|
||||||
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
||||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
||||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
||||||
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
||||||
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"`
|
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"`
|
||||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||||
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
||||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, or kubernetes). auto detects the environment." yaml:"labelProvider"`
|
||||||
|
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
Path string `description:"The path to the database, including file name." yaml:"path"`
|
Driver string `description:"The database driver to use. Valid values: sqlite, memory." yaml:"driver"`
|
||||||
|
Path string `description:"The path to the SQLite database, including file name. Only used when driver is sqlite." yaml:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnalyticsConfig struct {
|
type AnalyticsConfig struct {
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
entry, err := controller.oidc.GetAccessToken(c, controller.oidc.Hash(token))
|
entry, err := controller.oidc.GetAccessToken(c, controller.oidc.Hash(token))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == service.ErrTokenNotFound {
|
if errors.Is(err, service.ErrTokenNotFound) {
|
||||||
tlog.App.Warn().Msg("OIDC userinfo accessed with invalid token")
|
tlog.App.Warn().Msg("OIDC userinfo accessed with invalid token")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"error": "invalid_grant",
|
"error": "invalid_grant",
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -848,14 +847,10 @@ func TestOIDCController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app := bootstrap.NewBootstrapApp(config.Config{})
|
store := memory.New()
|
||||||
|
|
||||||
db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db"))
|
oidcService := service.NewOIDCService(oidcServiceCfg, store)
|
||||||
require.NoError(t, err)
|
err := oidcService.Init()
|
||||||
|
|
||||||
queries := repository.New(db)
|
|
||||||
oidcService := service.NewOIDCService(oidcServiceCfg, queries)
|
|
||||||
err = oidcService.Init()
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -877,9 +872,4 @@ func TestOIDCController(t *testing.T) {
|
|||||||
test.run(t, router, recorder)
|
test.run(t, router, recorder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = db.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package controller_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -18,7 +16,6 @@ import (
|
|||||||
|
|
||||||
func TestProxyController(t *testing.T) {
|
func TestProxyController(t *testing.T) {
|
||||||
tlog.NewTestLogger().Init()
|
tlog.NewTestLogger().Init()
|
||||||
tempDir := t.TempDir()
|
|
||||||
|
|
||||||
authServiceCfg := service.AuthServiceConfig{
|
authServiceCfg := service.AuthServiceConfig{
|
||||||
Users: []config.User{
|
Users: []config.User{
|
||||||
@@ -393,15 +390,10 @@ func TestProxyController(t *testing.T) {
|
|||||||
|
|
||||||
oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig)
|
oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig)
|
||||||
|
|
||||||
app := bootstrap.NewBootstrapApp(config.Config{})
|
store := memory.New()
|
||||||
|
|
||||||
db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
queries := repository.New(db)
|
|
||||||
|
|
||||||
docker := service.NewDockerService()
|
docker := service.NewDockerService()
|
||||||
err = docker.Init()
|
err := docker.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ldap := service.NewLdapService(service.LdapServiceConfig{})
|
ldap := service.NewLdapService(service.LdapServiceConfig{})
|
||||||
@@ -412,7 +404,7 @@ func TestProxyController(t *testing.T) {
|
|||||||
err = broker.Init()
|
err = broker.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
authService := service.NewAuthService(authServiceCfg, docker, ldap, queries, broker)
|
authService := service.NewAuthService(authServiceCfg, ldap, store, broker)
|
||||||
err = authService.Init()
|
err = authService.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -437,9 +429,4 @@ func TestProxyController(t *testing.T) {
|
|||||||
test.run(t, router, recorder)
|
test.run(t, router, recorder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = db.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,15 @@ package controller_test
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -22,7 +20,6 @@ import (
|
|||||||
|
|
||||||
func TestUserController(t *testing.T) {
|
func TestUserController(t *testing.T) {
|
||||||
tlog.NewTestLogger().Init()
|
tlog.NewTestLogger().Init()
|
||||||
tempDir := t.TempDir()
|
|
||||||
|
|
||||||
authServiceCfg := service.AuthServiceConfig{
|
authServiceCfg := service.AuthServiceConfig{
|
||||||
Users: []config.User{
|
Users: []config.User{
|
||||||
@@ -351,15 +348,10 @@ func TestUserController(t *testing.T) {
|
|||||||
|
|
||||||
oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig)
|
oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig)
|
||||||
|
|
||||||
app := bootstrap.NewBootstrapApp(config.Config{})
|
store := memory.New()
|
||||||
|
|
||||||
db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
queries := repository.New(db)
|
|
||||||
|
|
||||||
docker := service.NewDockerService()
|
docker := service.NewDockerService()
|
||||||
err = docker.Init()
|
err := docker.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ldap := service.NewLdapService(service.LdapServiceConfig{})
|
ldap := service.NewLdapService(service.LdapServiceConfig{})
|
||||||
@@ -370,7 +362,7 @@ func TestUserController(t *testing.T) {
|
|||||||
err = broker.Init()
|
err = broker.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
authService := service.NewAuthService(authServiceCfg, docker, ldap, queries, broker)
|
authService := service.NewAuthService(authServiceCfg, ldap, store, broker)
|
||||||
err = authService.Init()
|
err = authService.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -435,9 +427,4 @@ func TestUserController(t *testing.T) {
|
|||||||
test.run(t, router, recorder)
|
test.run(t, router, recorder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = db.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -101,15 +100,10 @@ func TestWellKnownController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app := bootstrap.NewBootstrapApp(config.Config{})
|
store := memory.New()
|
||||||
|
|
||||||
db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth.db"))
|
oidcService := service.NewOIDCService(oidcServiceCfg, store)
|
||||||
require.NoError(t, err)
|
err := oidcService.Init()
|
||||||
|
|
||||||
queries := repository.New(db)
|
|
||||||
|
|
||||||
oidcService := service.NewOIDCService(oidcServiceCfg, queries)
|
|
||||||
err = oidcService.Init()
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -125,9 +119,4 @@ func TestWellKnownController(t *testing.T) {
|
|||||||
test.run(t, router, recorder)
|
test.run(t, router, recorder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = db.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Store) CreateOidcCode(_ context.Context, arg repository.CreateOidcCodeParams) (repository.OidcCode, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
// Enforce sub UNIQUE constraint
|
||||||
|
for _, c := range s.oidcCodes {
|
||||||
|
if c.Sub == arg.Sub {
|
||||||
|
return repository.OidcCode{}, fmt.Errorf("UNIQUE constraint failed: oidc_codes.sub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code := repository.OidcCode(arg)
|
||||||
|
s.oidcCodes[arg.CodeHash] = code
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOidcCode is a destructive read: it deletes and returns the code (mirrors SQLite's DELETE...RETURNING).
|
||||||
|
func (s *Store) GetOidcCode(_ context.Context, codeHash string) (repository.OidcCode, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
c, ok := s.oidcCodes[codeHash]
|
||||||
|
if !ok {
|
||||||
|
return repository.OidcCode{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
delete(s.oidcCodes, codeHash)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOidcCodeBySub is a destructive read: it deletes and returns the code (mirrors SQLite's DELETE...RETURNING).
|
||||||
|
func (s *Store) GetOidcCodeBySub(_ context.Context, sub string) (repository.OidcCode, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, c := range s.oidcCodes {
|
||||||
|
if c.Sub == sub {
|
||||||
|
delete(s.oidcCodes, k)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repository.OidcCode{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOidcCodeUnsafe is a non-destructive read (mirrors SQLite's SELECT).
|
||||||
|
func (s *Store) GetOidcCodeUnsafe(_ context.Context, codeHash string) (repository.OidcCode, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
c, ok := s.oidcCodes[codeHash]
|
||||||
|
if !ok {
|
||||||
|
return repository.OidcCode{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOidcCodeBySubUnsafe is a non-destructive read (mirrors SQLite's SELECT).
|
||||||
|
func (s *Store) GetOidcCodeBySubUnsafe(_ context.Context, sub string) (repository.OidcCode, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
for _, c := range s.oidcCodes {
|
||||||
|
if c.Sub == sub {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repository.OidcCode{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcCode(_ context.Context, codeHash string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
delete(s.oidcCodes, codeHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcCodeBySub(_ context.Context, sub string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, c := range s.oidcCodes {
|
||||||
|
if c.Sub == sub {
|
||||||
|
delete(s.oidcCodes, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredOidcCodes(_ context.Context, expiresAt int64) ([]repository.OidcCode, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
var deleted []repository.OidcCode
|
||||||
|
for k, c := range s.oidcCodes {
|
||||||
|
if c.ExpiresAt < expiresAt {
|
||||||
|
deleted = append(deleted, c)
|
||||||
|
delete(s.oidcCodes, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateOidcToken(_ context.Context, arg repository.CreateOidcTokenParams) (repository.OidcToken, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
// Enforce sub UNIQUE constraint
|
||||||
|
for _, t := range s.oidcTokens {
|
||||||
|
if t.Sub == arg.Sub {
|
||||||
|
return repository.OidcToken{}, fmt.Errorf("UNIQUE constraint failed: oidc_tokens.sub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tok := repository.OidcToken{
|
||||||
|
Sub: arg.Sub,
|
||||||
|
AccessTokenHash: arg.AccessTokenHash,
|
||||||
|
RefreshTokenHash: arg.RefreshTokenHash,
|
||||||
|
CodeHash: arg.CodeHash,
|
||||||
|
Scope: arg.Scope,
|
||||||
|
ClientID: arg.ClientID,
|
||||||
|
TokenExpiresAt: arg.TokenExpiresAt,
|
||||||
|
RefreshTokenExpiresAt: arg.RefreshTokenExpiresAt,
|
||||||
|
Nonce: arg.Nonce,
|
||||||
|
}
|
||||||
|
s.oidcTokens[arg.AccessTokenHash] = tok
|
||||||
|
return tok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcToken(_ context.Context, accessTokenHash string) (repository.OidcToken, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
t, ok := s.oidcTokens[accessTokenHash]
|
||||||
|
if !ok {
|
||||||
|
return repository.OidcToken{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcTokenByRefreshToken(_ context.Context, refreshTokenHash string) (repository.OidcToken, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
for _, t := range s.oidcTokens {
|
||||||
|
if t.RefreshTokenHash == refreshTokenHash {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repository.OidcToken{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcTokenBySub(_ context.Context, sub string) (repository.OidcToken, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
for _, t := range s.oidcTokens {
|
||||||
|
if t.Sub == sub {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repository.OidcToken{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateOidcTokenByRefreshToken(_ context.Context, arg repository.UpdateOidcTokenByRefreshTokenParams) (repository.OidcToken, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, t := range s.oidcTokens {
|
||||||
|
if t.RefreshTokenHash == arg.RefreshTokenHash_2 {
|
||||||
|
delete(s.oidcTokens, k)
|
||||||
|
t.AccessTokenHash = arg.AccessTokenHash
|
||||||
|
t.RefreshTokenHash = arg.RefreshTokenHash
|
||||||
|
t.TokenExpiresAt = arg.TokenExpiresAt
|
||||||
|
t.RefreshTokenExpiresAt = arg.RefreshTokenExpiresAt
|
||||||
|
s.oidcTokens[arg.AccessTokenHash] = t
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repository.OidcToken{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcToken(_ context.Context, accessTokenHash string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
delete(s.oidcTokens, accessTokenHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcTokenBySub(_ context.Context, sub string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, t := range s.oidcTokens {
|
||||||
|
if t.Sub == sub {
|
||||||
|
delete(s.oidcTokens, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcTokenByCodeHash(_ context.Context, codeHash string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, t := range s.oidcTokens {
|
||||||
|
if t.CodeHash == codeHash {
|
||||||
|
delete(s.oidcTokens, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredOidcTokens(_ context.Context, arg repository.DeleteExpiredOidcTokensParams) ([]repository.OidcToken, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
var deleted []repository.OidcToken
|
||||||
|
for k, t := range s.oidcTokens {
|
||||||
|
if t.TokenExpiresAt < arg.TokenExpiresAt || t.RefreshTokenExpiresAt < arg.RefreshTokenExpiresAt {
|
||||||
|
deleted = append(deleted, t)
|
||||||
|
delete(s.oidcTokens, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateOidcUserInfo(_ context.Context, arg repository.CreateOidcUserInfoParams) (repository.OidcUserinfo, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
u := repository.OidcUserinfo(arg)
|
||||||
|
s.oidcUsers[arg.Sub] = u
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcUserInfo(_ context.Context, sub string) (repository.OidcUserinfo, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
u, ok := s.oidcUsers[sub]
|
||||||
|
if !ok {
|
||||||
|
return repository.OidcUserinfo{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcUserInfo(_ context.Context, sub string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
delete(s.oidcUsers, sub)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Store) CreateSession(_ context.Context, arg repository.CreateSessionParams) (repository.Session, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
sess := repository.Session(arg)
|
||||||
|
s.sessions[arg.UUID] = sess
|
||||||
|
return sess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetSession(_ context.Context, uuid string) (repository.Session, error) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
sess, ok := s.sessions[uuid]
|
||||||
|
if !ok {
|
||||||
|
return repository.Session{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
return sess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateSession(_ context.Context, arg repository.UpdateSessionParams) (repository.Session, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
sess, ok := s.sessions[arg.UUID]
|
||||||
|
if !ok {
|
||||||
|
return repository.Session{}, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
sess.Username = arg.Username
|
||||||
|
sess.Email = arg.Email
|
||||||
|
sess.Name = arg.Name
|
||||||
|
sess.Provider = arg.Provider
|
||||||
|
sess.TotpPending = arg.TotpPending
|
||||||
|
sess.OAuthGroups = arg.OAuthGroups
|
||||||
|
sess.Expiry = arg.Expiry
|
||||||
|
sess.OAuthName = arg.OAuthName
|
||||||
|
sess.OAuthSub = arg.OAuthSub
|
||||||
|
s.sessions[arg.UUID] = sess
|
||||||
|
return sess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteSession(_ context.Context, uuid string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
delete(s.sessions, uuid)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredSessions(_ context.Context, expiry int64) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for k, v := range s.sessions {
|
||||||
|
if v.Expiry < expiry {
|
||||||
|
delete(s.sessions, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Package memory provides an in-memory implementation of repository.Store for use in tests.
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store is a thread-safe in-memory implementation of repository.Store.
|
||||||
|
type Store struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
sessions map[string]repository.Session
|
||||||
|
oidcCodes map[string]repository.OidcCode
|
||||||
|
oidcTokens map[string]repository.OidcToken
|
||||||
|
oidcUsers map[string]repository.OidcUserinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new empty in-memory Store.
|
||||||
|
func New() repository.Store {
|
||||||
|
return &Store{
|
||||||
|
sessions: make(map[string]repository.Session),
|
||||||
|
oidcCodes: make(map[string]repository.OidcCode),
|
||||||
|
oidcTokens: make(map[string]repository.OidcToken),
|
||||||
|
oidcUsers: make(map[string]repository.OidcUserinfo),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,22 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.30.0
|
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
|
// Shared model and parameter types for all storage drivers.
|
||||||
|
// sqlc-generated driver packages use these via the conversion layer in their store.go.
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
UUID string
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
TotpPending bool
|
||||||
|
OAuthGroups string
|
||||||
|
Expiry int64
|
||||||
|
CreatedAt int64
|
||||||
|
OAuthName string
|
||||||
|
OAuthSub string
|
||||||
|
}
|
||||||
|
|
||||||
type OidcCode struct {
|
type OidcCode struct {
|
||||||
Sub string
|
Sub string
|
||||||
CodeHash string
|
CodeHash string
|
||||||
@@ -49,7 +62,7 @@ type OidcUserinfo struct {
|
|||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type CreateSessionParams struct {
|
||||||
UUID string
|
UUID string
|
||||||
Username string
|
Username string
|
||||||
Email string
|
Email string
|
||||||
@@ -62,3 +75,74 @@ type Session struct {
|
|||||||
OAuthName string
|
OAuthName string
|
||||||
OAuthSub string
|
OAuthSub string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateSessionParams struct {
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
TotpPending bool
|
||||||
|
OAuthGroups string
|
||||||
|
Expiry int64
|
||||||
|
OAuthName string
|
||||||
|
OAuthSub string
|
||||||
|
UUID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOidcCodeParams struct {
|
||||||
|
Sub string
|
||||||
|
CodeHash string
|
||||||
|
Scope string
|
||||||
|
RedirectURI string
|
||||||
|
ClientID string
|
||||||
|
ExpiresAt int64
|
||||||
|
Nonce string
|
||||||
|
CodeChallenge string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOidcTokenParams struct {
|
||||||
|
Sub string
|
||||||
|
AccessTokenHash string
|
||||||
|
RefreshTokenHash string
|
||||||
|
Scope string
|
||||||
|
ClientID string
|
||||||
|
TokenExpiresAt int64
|
||||||
|
RefreshTokenExpiresAt int64
|
||||||
|
CodeHash string
|
||||||
|
Nonce string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateOidcTokenByRefreshTokenParams struct {
|
||||||
|
AccessTokenHash string
|
||||||
|
RefreshTokenHash string
|
||||||
|
TokenExpiresAt int64
|
||||||
|
RefreshTokenExpiresAt int64
|
||||||
|
RefreshTokenHash_2 string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteExpiredOidcTokensParams struct {
|
||||||
|
TokenExpiresAt int64
|
||||||
|
RefreshTokenExpiresAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOidcUserInfoParams struct {
|
||||||
|
Sub string
|
||||||
|
Name string
|
||||||
|
PreferredUsername string
|
||||||
|
Email string
|
||||||
|
Groups string
|
||||||
|
UpdatedAt int64
|
||||||
|
GivenName string
|
||||||
|
FamilyName string
|
||||||
|
MiddleName string
|
||||||
|
Nickname string
|
||||||
|
Profile string
|
||||||
|
Picture string
|
||||||
|
Website string
|
||||||
|
Gender string
|
||||||
|
Birthdate string
|
||||||
|
Zoneinfo string
|
||||||
|
Locale string
|
||||||
|
PhoneNumber string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.0
|
||||||
|
|
||||||
package repository
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
//go:generate go run github.com/tinyauthapp/tinyauth/cmd/gen/sqlc-wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/sqlite
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.31.0
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
type OidcCode struct {
|
||||||
|
Sub string
|
||||||
|
CodeHash string
|
||||||
|
Scope string
|
||||||
|
RedirectURI string
|
||||||
|
ClientID string
|
||||||
|
ExpiresAt int64
|
||||||
|
Nonce string
|
||||||
|
CodeChallenge string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcToken struct {
|
||||||
|
Sub string
|
||||||
|
AccessTokenHash string
|
||||||
|
RefreshTokenHash string
|
||||||
|
CodeHash string
|
||||||
|
Scope string
|
||||||
|
ClientID string
|
||||||
|
TokenExpiresAt int64
|
||||||
|
RefreshTokenExpiresAt int64
|
||||||
|
Nonce string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcUserinfo struct {
|
||||||
|
Sub string
|
||||||
|
Name string
|
||||||
|
PreferredUsername string
|
||||||
|
Email string
|
||||||
|
Groups string
|
||||||
|
UpdatedAt int64
|
||||||
|
GivenName string
|
||||||
|
FamilyName string
|
||||||
|
MiddleName string
|
||||||
|
Nickname string
|
||||||
|
Profile string
|
||||||
|
Picture string
|
||||||
|
Website string
|
||||||
|
Gender string
|
||||||
|
Birthdate string
|
||||||
|
Zoneinfo string
|
||||||
|
Locale string
|
||||||
|
PhoneNumber string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
UUID string
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
TotpPending bool
|
||||||
|
OAuthGroups string
|
||||||
|
Expiry int64
|
||||||
|
CreatedAt int64
|
||||||
|
OAuthName string
|
||||||
|
OAuthSub string
|
||||||
|
}
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.0
|
||||||
// source: oidc_queries.sql
|
// source: oidc_queries.sql
|
||||||
|
|
||||||
package repository
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.0
|
||||||
// source: session_queries.sql
|
// source: session_queries.sql
|
||||||
|
|
||||||
package repository
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
// Code generated by cmd/gen/sqlc-wrapper. DO NOT EDIT.
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store wraps *Queries and implements repository.Store.
|
||||||
|
type Store struct {
|
||||||
|
q *Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore wraps a *Queries to satisfy repository.Store.
|
||||||
|
func NewStore(q *Queries) repository.Store {
|
||||||
|
return &Store{q: q}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMap = []struct {
|
||||||
|
from error
|
||||||
|
to error
|
||||||
|
}{
|
||||||
|
{sql.ErrNoRows, repository.ErrNotFound},
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapErr(err error) error {
|
||||||
|
for _, e := range errMap {
|
||||||
|
if errors.Is(err, e.from) {
|
||||||
|
return e.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcCodeToRepo(v OidcCode) repository.OidcCode {
|
||||||
|
return repository.OidcCode(v)
|
||||||
|
}
|
||||||
|
func oidcTokenToRepo(v OidcToken) repository.OidcToken {
|
||||||
|
return repository.OidcToken(v)
|
||||||
|
}
|
||||||
|
func oidcUserinfoToRepo(v OidcUserinfo) repository.OidcUserinfo {
|
||||||
|
return repository.OidcUserinfo(v)
|
||||||
|
}
|
||||||
|
func sessionToRepo(v Session) repository.Session {
|
||||||
|
return repository.Session(v)
|
||||||
|
}
|
||||||
|
func (s *Store) CreateOidcCode(ctx context.Context, arg repository.CreateOidcCodeParams) (repository.OidcCode, error) {
|
||||||
|
r, err := s.q.CreateOidcCode(ctx, CreateOidcCodeParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcCode{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcCodeToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateOidcToken(ctx context.Context, arg repository.CreateOidcTokenParams) (repository.OidcToken, error) {
|
||||||
|
r, err := s.q.CreateOidcToken(ctx, CreateOidcTokenParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcToken{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcTokenToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateOidcUserInfo(ctx context.Context, arg repository.CreateOidcUserInfoParams) (repository.OidcUserinfo, error) {
|
||||||
|
r, err := s.q.CreateOidcUserInfo(ctx, CreateOidcUserInfoParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcUserinfo{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcUserinfoToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateSession(ctx context.Context, arg repository.CreateSessionParams) (repository.Session, error) {
|
||||||
|
r, err := s.q.CreateSession(ctx, CreateSessionParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.Session{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return sessionToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]repository.OidcCode, error) {
|
||||||
|
rows, err := s.q.DeleteExpiredOidcCodes(ctx, expiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapErr(err)
|
||||||
|
}
|
||||||
|
out := make([]repository.OidcCode, len(rows))
|
||||||
|
for i, row := range rows {
|
||||||
|
out[i] = oidcCodeToRepo(row)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredOidcTokens(ctx context.Context, arg repository.DeleteExpiredOidcTokensParams) ([]repository.OidcToken, error) {
|
||||||
|
rows, err := s.q.DeleteExpiredOidcTokens(ctx, DeleteExpiredOidcTokensParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapErr(err)
|
||||||
|
}
|
||||||
|
out := make([]repository.OidcToken, len(rows))
|
||||||
|
for i, row := range rows {
|
||||||
|
out[i] = oidcTokenToRepo(row)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteExpiredSessions(ctx context.Context, expiry int64) error {
|
||||||
|
return mapErr(s.q.DeleteExpiredSessions(ctx, expiry))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcCode(ctx context.Context, codeHash string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcCode(ctx, codeHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcCodeBySub(ctx context.Context, sub string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcCodeBySub(ctx, sub))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcToken(ctx context.Context, accessTokenHash string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcToken(ctx, accessTokenHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcTokenByCodeHash(ctx context.Context, codeHash string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcTokenByCodeHash(ctx, codeHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcTokenBySub(ctx context.Context, sub string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcTokenBySub(ctx, sub))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteOidcUserInfo(ctx context.Context, sub string) error {
|
||||||
|
return mapErr(s.q.DeleteOidcUserInfo(ctx, sub))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteSession(ctx context.Context, uuid string) error {
|
||||||
|
return mapErr(s.q.DeleteSession(ctx, uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcCode(ctx context.Context, codeHash string) (repository.OidcCode, error) {
|
||||||
|
r, err := s.q.GetOidcCode(ctx, codeHash)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcCode{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcCodeToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcCodeBySub(ctx context.Context, sub string) (repository.OidcCode, error) {
|
||||||
|
r, err := s.q.GetOidcCodeBySub(ctx, sub)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcCode{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcCodeToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcCodeBySubUnsafe(ctx context.Context, sub string) (repository.OidcCode, error) {
|
||||||
|
r, err := s.q.GetOidcCodeBySubUnsafe(ctx, sub)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcCode{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcCodeToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcCodeUnsafe(ctx context.Context, codeHash string) (repository.OidcCode, error) {
|
||||||
|
r, err := s.q.GetOidcCodeUnsafe(ctx, codeHash)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcCode{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcCodeToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcToken(ctx context.Context, accessTokenHash string) (repository.OidcToken, error) {
|
||||||
|
r, err := s.q.GetOidcToken(ctx, accessTokenHash)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcToken{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcTokenToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcTokenByRefreshToken(ctx context.Context, refreshTokenHash string) (repository.OidcToken, error) {
|
||||||
|
r, err := s.q.GetOidcTokenByRefreshToken(ctx, refreshTokenHash)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcToken{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcTokenToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcTokenBySub(ctx context.Context, sub string) (repository.OidcToken, error) {
|
||||||
|
r, err := s.q.GetOidcTokenBySub(ctx, sub)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcToken{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcTokenToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetOidcUserInfo(ctx context.Context, sub string) (repository.OidcUserinfo, error) {
|
||||||
|
r, err := s.q.GetOidcUserInfo(ctx, sub)
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcUserinfo{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcUserinfoToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetSession(ctx context.Context, uuid string) (repository.Session, error) {
|
||||||
|
r, err := s.q.GetSession(ctx, uuid)
|
||||||
|
if err != nil {
|
||||||
|
return repository.Session{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return sessionToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateOidcTokenByRefreshToken(ctx context.Context, arg repository.UpdateOidcTokenByRefreshTokenParams) (repository.OidcToken, error) {
|
||||||
|
r, err := s.q.UpdateOidcTokenByRefreshToken(ctx, UpdateOidcTokenByRefreshTokenParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.OidcToken{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return oidcTokenToRepo(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateSession(ctx context.Context, arg repository.UpdateSessionParams) (repository.Session, error) {
|
||||||
|
r, err := s.q.UpdateSession(ctx, UpdateSessionParams(arg))
|
||||||
|
if err != nil {
|
||||||
|
return repository.Session{}, mapErr(err)
|
||||||
|
}
|
||||||
|
return sessionToRepo(r), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotFound is returned by Store methods when the requested record does not exist.
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
// Store is the interface that all storage drivers must implement.
|
||||||
|
// The sqlc-generated *Queries struct satisfies this interface for SQLite.
|
||||||
|
// Future drivers (postgres, etc.) must return the shared types defined in this package.
|
||||||
|
type Store interface {
|
||||||
|
// Sessions
|
||||||
|
CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error)
|
||||||
|
GetSession(ctx context.Context, uuid string) (Session, error)
|
||||||
|
UpdateSession(ctx context.Context, arg UpdateSessionParams) (Session, error)
|
||||||
|
DeleteSession(ctx context.Context, uuid string) error
|
||||||
|
DeleteExpiredSessions(ctx context.Context, expiry int64) error
|
||||||
|
|
||||||
|
// OIDC codes
|
||||||
|
CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error)
|
||||||
|
GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error)
|
||||||
|
GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error)
|
||||||
|
GetOidcCodeUnsafe(ctx context.Context, codeHash string) (OidcCode, error)
|
||||||
|
GetOidcCodeBySubUnsafe(ctx context.Context, sub string) (OidcCode, error)
|
||||||
|
DeleteOidcCode(ctx context.Context, codeHash string) error
|
||||||
|
DeleteOidcCodeBySub(ctx context.Context, sub string) error
|
||||||
|
DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error)
|
||||||
|
|
||||||
|
// OIDC tokens
|
||||||
|
CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams) (OidcToken, error)
|
||||||
|
GetOidcToken(ctx context.Context, accessTokenHash string) (OidcToken, error)
|
||||||
|
GetOidcTokenByRefreshToken(ctx context.Context, refreshTokenHash string) (OidcToken, error)
|
||||||
|
GetOidcTokenBySub(ctx context.Context, sub string) (OidcToken, error)
|
||||||
|
UpdateOidcTokenByRefreshToken(ctx context.Context, arg UpdateOidcTokenByRefreshTokenParams) (OidcToken, error)
|
||||||
|
DeleteOidcToken(ctx context.Context, accessTokenHash string) error
|
||||||
|
DeleteOidcTokenBySub(ctx context.Context, sub string) error
|
||||||
|
DeleteOidcTokenByCodeHash(ctx context.Context, codeHash string) error
|
||||||
|
DeleteExpiredOidcTokens(ctx context.Context, arg DeleteExpiredOidcTokensParams) ([]OidcToken, error)
|
||||||
|
|
||||||
|
// OIDC userinfo
|
||||||
|
CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfoParams) (OidcUserinfo, error)
|
||||||
|
GetOidcUserInfo(ctx context.Context, sub string) (OidcUserinfo, error)
|
||||||
|
DeleteOidcUserInfo(ctx context.Context, sub string) error
|
||||||
|
}
|
||||||
@@ -8,15 +8,19 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccessControlsService struct {
|
type LabelProvider interface {
|
||||||
docker *DockerService
|
GetLabels(appDomain string) (config.App, error)
|
||||||
static map[string]config.App
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccessControlsService(docker *DockerService, static map[string]config.App) *AccessControlsService {
|
type AccessControlsService struct {
|
||||||
|
labelProvider LabelProvider
|
||||||
|
static map[string]config.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccessControlsService(labelProvider LabelProvider, static map[string]config.App) *AccessControlsService {
|
||||||
return &AccessControlsService{
|
return &AccessControlsService{
|
||||||
docker: docker,
|
labelProvider: labelProvider,
|
||||||
static: static,
|
static: static,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ func (acls *AccessControlsService) GetAccessControls(domain string) (config.App,
|
|||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to Docker labels
|
// Fallback to label provider
|
||||||
tlog.App.Debug().Msg("Falling back to Docker labels for ACLs")
|
tlog.App.Debug().Msg("Falling back to label provider for ACLs")
|
||||||
return acls.docker.GetLabels(domain)
|
return acls.labelProvider.GetLabels(domain)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -15,10 +14,11 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,7 +82,6 @@ type AuthServiceConfig struct {
|
|||||||
|
|
||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
config AuthServiceConfig
|
config AuthServiceConfig
|
||||||
docker *DockerService
|
|
||||||
loginAttempts map[string]*LoginAttempt
|
loginAttempts map[string]*LoginAttempt
|
||||||
ldapGroupsCache map[string]*LdapGroupsCache
|
ldapGroupsCache map[string]*LdapGroupsCache
|
||||||
oauthPendingSessions map[string]*OAuthPendingSession
|
oauthPendingSessions map[string]*OAuthPendingSession
|
||||||
@@ -90,24 +89,23 @@ type AuthService struct {
|
|||||||
loginMutex sync.RWMutex
|
loginMutex sync.RWMutex
|
||||||
ldapGroupsMutex sync.RWMutex
|
ldapGroupsMutex sync.RWMutex
|
||||||
ldap *LdapService
|
ldap *LdapService
|
||||||
queries *repository.Queries
|
queries repository.Store
|
||||||
oauthBroker *OAuthBrokerService
|
oauthBroker *OAuthBrokerService
|
||||||
lockdown *Lockdown
|
lockdown *Lockdown
|
||||||
lockdownCtx context.Context
|
lockdownCtx context.Context
|
||||||
lockdownCancelFunc context.CancelFunc
|
lockdownCancelFunc context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, queries *repository.Queries, oauthBroker *OAuthBrokerService) *AuthService {
|
func NewAuthService(config AuthServiceConfig, ldap *LdapService, queries repository.Store, oauthBroker *OAuthBrokerService) *AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
config: config,
|
config: config,
|
||||||
docker: docker,
|
|
||||||
loginAttempts: make(map[string]*LoginAttempt),
|
loginAttempts: make(map[string]*LoginAttempt),
|
||||||
ldapGroupsCache: make(map[string]*LdapGroupsCache),
|
ldapGroupsCache: make(map[string]*LdapGroupsCache),
|
||||||
oauthPendingSessions: make(map[string]*OAuthPendingSession),
|
oauthPendingSessions: make(map[string]*OAuthPendingSession),
|
||||||
ldap: ldap,
|
ldap: ldap,
|
||||||
queries: queries,
|
queries: queries,
|
||||||
oauthBroker: oauthBroker,
|
oauthBroker: oauthBroker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *AuthService) Init() error {
|
func (auth *AuthService) Init() error {
|
||||||
@@ -412,7 +410,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
|
|||||||
session, err := auth.queries.GetSession(c, cookie)
|
session, err := auth.queries.GetSession(c, cookie)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
return repository.Session{}, fmt.Errorf("session not found")
|
return repository.Session{}, fmt.Errorf("session not found")
|
||||||
}
|
}
|
||||||
return repository.Session{}, err
|
return repository.Session{}, err
|
||||||
|
|||||||
@@ -0,0 +1,303 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ingressKey struct {
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ingressAppKey struct {
|
||||||
|
ingressKey
|
||||||
|
appName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ingressApp struct {
|
||||||
|
domain string
|
||||||
|
appName string
|
||||||
|
app config.App
|
||||||
|
}
|
||||||
|
|
||||||
|
type KubernetesService struct {
|
||||||
|
client dynamic.Interface
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
started bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
ingressApps map[ingressKey][]ingressApp
|
||||||
|
domainIndex map[string]ingressAppKey
|
||||||
|
appNameIndex map[string]ingressAppKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKubernetesService() *KubernetesService {
|
||||||
|
return &KubernetesService{
|
||||||
|
ingressApps: make(map[ingressKey][]ingressApp),
|
||||||
|
domainIndex: make(map[string]ingressAppKey),
|
||||||
|
appNameIndex: make(map[string]ingressAppKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) addIngressApps(namespace, name string, apps []ingressApp) {
|
||||||
|
k.mu.Lock()
|
||||||
|
defer k.mu.Unlock()
|
||||||
|
|
||||||
|
key := ingressKey{namespace, name}
|
||||||
|
// Remove existing entries for this ingress
|
||||||
|
if existing, ok := k.ingressApps[key]; ok {
|
||||||
|
for _, app := range existing {
|
||||||
|
delete(k.domainIndex, app.domain)
|
||||||
|
delete(k.appNameIndex, app.appName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add new entries
|
||||||
|
k.ingressApps[key] = apps
|
||||||
|
for _, app := range apps {
|
||||||
|
appKey := ingressAppKey{key, app.appName}
|
||||||
|
k.domainIndex[app.domain] = appKey
|
||||||
|
k.appNameIndex[app.appName] = appKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) removeIngress(namespace, name string) {
|
||||||
|
k.mu.Lock()
|
||||||
|
defer k.mu.Unlock()
|
||||||
|
|
||||||
|
key := ingressKey{namespace, name}
|
||||||
|
if apps, ok := k.ingressApps[key]; ok {
|
||||||
|
for _, app := range apps {
|
||||||
|
delete(k.domainIndex, app.domain)
|
||||||
|
delete(k.appNameIndex, app.appName)
|
||||||
|
}
|
||||||
|
delete(k.ingressApps, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) getByDomain(domain string) (config.App, bool) {
|
||||||
|
k.mu.RLock()
|
||||||
|
defer k.mu.RUnlock()
|
||||||
|
|
||||||
|
if appKey, ok := k.domainIndex[domain]; ok {
|
||||||
|
if apps, ok := k.ingressApps[appKey.ingressKey]; ok {
|
||||||
|
for _, app := range apps {
|
||||||
|
if app.domain == domain && app.appName == appKey.appName {
|
||||||
|
return app.app, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.App{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) getByAppName(appName string) (config.App, bool) {
|
||||||
|
k.mu.RLock()
|
||||||
|
defer k.mu.RUnlock()
|
||||||
|
|
||||||
|
if appKey, ok := k.appNameIndex[appName]; ok {
|
||||||
|
if apps, ok := k.ingressApps[appKey.ingressKey]; ok {
|
||||||
|
for _, app := range apps {
|
||||||
|
if app.appName == appName {
|
||||||
|
return app.app, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.App{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||||
|
namespace := item.GetNamespace()
|
||||||
|
name := item.GetName()
|
||||||
|
annotations := item.GetAnnotations()
|
||||||
|
if annotations == nil {
|
||||||
|
k.removeIngress(namespace, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labels, err := decoders.DecodeLabels[config.Apps](annotations, "apps")
|
||||||
|
if err != nil {
|
||||||
|
tlog.App.Debug().Err(err).Msg("Failed to decode labels from annotations")
|
||||||
|
k.removeIngress(namespace, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var apps []ingressApp
|
||||||
|
for appName, appLabels := range labels.Apps {
|
||||||
|
if appLabels.Config.Domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
apps = append(apps, ingressApp{
|
||||||
|
domain: appLabels.Config.Domain,
|
||||||
|
appName: appName,
|
||||||
|
app: appLabels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(apps) == 0 {
|
||||||
|
k.removeIngress(namespace, name)
|
||||||
|
} else {
|
||||||
|
k.addIngressApps(namespace, name, apps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource) error {
|
||||||
|
ctx, cancel := context.WithTimeout(k.ctx, 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
list, err := k.client.Resource(gvr).List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
tlog.App.Debug().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to list ingresses during resync")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range list.Items {
|
||||||
|
k.updateFromItem(&list.Items[i])
|
||||||
|
}
|
||||||
|
tlog.App.Debug().Str("api", gvr.GroupVersion().String()).Int("count", len(list.Items)).Msg("Resynced ingress cache")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runWatcher drains events from an active watcher until it closes or the context is done.
|
||||||
|
// Returns true if the caller should restart the watcher, false if it should exit.
|
||||||
|
func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.Interface, resyncTicker *time.Ticker) bool {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-k.ctx.Done():
|
||||||
|
w.Stop()
|
||||||
|
return false
|
||||||
|
case event, ok := <-w.ResultChan():
|
||||||
|
if !ok {
|
||||||
|
tlog.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Watcher channel closed, restarting in 5 seconds")
|
||||||
|
w.Stop()
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
item, ok := event.Object.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
tlog.App.Warn().Str("api", gvr.GroupVersion().String()).Msg("Failed to cast watched object")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch event.Type {
|
||||||
|
case watch.Added, watch.Modified:
|
||||||
|
k.updateFromItem(item)
|
||||||
|
case watch.Deleted:
|
||||||
|
k.removeIngress(item.GetNamespace(), item.GetName())
|
||||||
|
}
|
||||||
|
case <-resyncTicker.C:
|
||||||
|
if err := k.resyncGVR(gvr); err != nil {
|
||||||
|
tlog.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource) {
|
||||||
|
resyncTicker := time.NewTicker(5 * time.Minute)
|
||||||
|
defer resyncTicker.Stop()
|
||||||
|
|
||||||
|
if err := k.resyncGVR(gvr); err != nil {
|
||||||
|
tlog.App.Error().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Initial resync failed, retrying in 30 seconds")
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-k.ctx.Done():
|
||||||
|
tlog.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Stopping watcher")
|
||||||
|
return
|
||||||
|
case <-resyncTicker.C:
|
||||||
|
if err := k.resyncGVR(gvr); err != nil {
|
||||||
|
tlog.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ctx, cancel := context.WithCancel(k.ctx)
|
||||||
|
watcher, err := k.client.Resource(gvr).Watch(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
tlog.App.Error().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to start watcher")
|
||||||
|
cancel()
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tlog.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Watcher started")
|
||||||
|
if !k.runWatcher(gvr, watcher, resyncTicker) {
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) Init() error {
|
||||||
|
var cfg *rest.Config
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cfg, err = rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get in-cluster Kubernetes config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dynamic.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Kubernetes client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k.client = client
|
||||||
|
k.ctx, k.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
gvr := schema.GroupVersionResource{
|
||||||
|
Group: "networking.k8s.io",
|
||||||
|
Version: "v1",
|
||||||
|
Resource: "ingresses",
|
||||||
|
}
|
||||||
|
|
||||||
|
accessCtx, accessCancel := context.WithTimeout(k.ctx, 5*time.Second)
|
||||||
|
defer accessCancel()
|
||||||
|
_, err = k.client.Resource(gvr).List(accessCtx, metav1.ListOptions{Limit: 1})
|
||||||
|
if err != nil {
|
||||||
|
tlog.App.Warn().Err(err).Msg("Insufficient permissions for networking.k8s.io/v1 Ingress, Kubernetes label provider will not work")
|
||||||
|
k.started = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlog.App.Debug().Msg("networking.k8s.io/v1 Ingress API accessible")
|
||||||
|
go k.watchGVR(gvr)
|
||||||
|
|
||||||
|
k.started = true
|
||||||
|
tlog.App.Info().Msg("Kubernetes label provider initialized")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) GetLabels(appDomain string) (config.App, error) {
|
||||||
|
if !k.started {
|
||||||
|
tlog.App.Debug().Msg("Kubernetes not connected, returning empty labels")
|
||||||
|
return config.App{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check cache
|
||||||
|
if app, found := k.getByDomain(appDomain); found {
|
||||||
|
tlog.App.Debug().Str("domain", appDomain).Msg("Found labels in cache by domain")
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
appName := strings.SplitN(appDomain, ".", 2)[0]
|
||||||
|
if app, found := k.getByAppName(appName); found {
|
||||||
|
tlog.App.Debug().Str("domain", appDomain).Str("appName", appName).Msg("Found labels in cache by app name")
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlog.App.Debug().Str("domain", appDomain).Msg("Cache miss, no matching ingress found")
|
||||||
|
return config.App{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKubernetesService(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
run func(t *testing.T, svc *KubernetesService)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
description: "Cache by domain returns app and misses unknown domain",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "foo.example.com"}}
|
||||||
|
svc.addIngressApps("default", "my-ingress", []ingressApp{
|
||||||
|
{domain: "foo.example.com", appName: "foo", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
got, ok := svc.getByDomain("foo.example.com")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "foo.example.com", got.Config.Domain)
|
||||||
|
|
||||||
|
_, ok = svc.getByDomain("notfound.example.com")
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Cache by app name returns app and misses unknown name",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "bar.example.com"}}
|
||||||
|
svc.addIngressApps("default", "my-ingress", []ingressApp{
|
||||||
|
{domain: "bar.example.com", appName: "bar", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
got, ok := svc.getByAppName("bar")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "bar.example.com", got.Config.Domain)
|
||||||
|
|
||||||
|
_, ok = svc.getByAppName("notfound")
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "RemoveIngress clears domain and app name entries",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "baz.example.com"}}
|
||||||
|
svc.addIngressApps("default", "my-ingress", []ingressApp{
|
||||||
|
{domain: "baz.example.com", appName: "baz", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
svc.removeIngress("default", "my-ingress")
|
||||||
|
|
||||||
|
_, ok := svc.getByDomain("baz.example.com")
|
||||||
|
assert.False(t, ok)
|
||||||
|
_, ok = svc.getByAppName("baz")
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "AddIngressApps replaces stale entries for the same ingress",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
old := config.App{Config: config.AppConfig{Domain: "old.example.com"}}
|
||||||
|
svc.addIngressApps("default", "my-ingress", []ingressApp{
|
||||||
|
{domain: "old.example.com", appName: "old", app: old},
|
||||||
|
})
|
||||||
|
|
||||||
|
updated := config.App{Config: config.AppConfig{Domain: "new.example.com"}}
|
||||||
|
svc.addIngressApps("default", "my-ingress", []ingressApp{
|
||||||
|
{domain: "new.example.com", appName: "new", app: updated},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, ok := svc.getByDomain("old.example.com")
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
got, ok := svc.getByDomain("new.example.com")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "new.example.com", got.Config.Domain)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "GetLabels returns app from cache when started",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
svc.started = true
|
||||||
|
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "hit.example.com"}}
|
||||||
|
svc.addIngressApps("default", "ing", []ingressApp{
|
||||||
|
{domain: "hit.example.com", appName: "hit", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
got, err := svc.GetLabels("hit.example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "hit.example.com", got.Config.Domain)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "GetLabels returns empty app on cache miss when started",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
svc.started = true
|
||||||
|
|
||||||
|
got, err := svc.GetLabels("notfound.example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, config.App{}, got)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "GetLabels resolves app by app name",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
svc.started = true
|
||||||
|
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "myapp.internal.example.com"}}
|
||||||
|
svc.addIngressApps("default", "ing", []ingressApp{
|
||||||
|
{domain: "myapp.internal.example.com", appName: "myapp", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
got, err := svc.GetLabels("myapp.internal.example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "myapp.internal.example.com", got.Config.Domain)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "GetLabels returns empty app when service not yet started",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
got, err := svc.GetLabels("anything.example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, config.App{}, got)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "UpdateFromItem parses annotations and populates cache",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
item := unstructured.Unstructured{}
|
||||||
|
item.SetNamespace("default")
|
||||||
|
item.SetName("test-ingress")
|
||||||
|
item.SetAnnotations(map[string]string{
|
||||||
|
"tinyauth.apps.myapp.config.domain": "myapp.example.com",
|
||||||
|
"tinyauth.apps.myapp.users.allow": "alice",
|
||||||
|
})
|
||||||
|
|
||||||
|
svc.updateFromItem(&item)
|
||||||
|
|
||||||
|
got, ok := svc.getByDomain("myapp.example.com")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "myapp.example.com", got.Config.Domain)
|
||||||
|
assert.Equal(t, "alice", got.Users.Allow)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "UpdateFromItem with no annotations removes existing cache entries",
|
||||||
|
run: func(t *testing.T, svc *KubernetesService) {
|
||||||
|
app := config.App{Config: config.AppConfig{Domain: "todelete.example.com"}}
|
||||||
|
svc.addIngressApps("default", "test-ingress", []ingressApp{
|
||||||
|
{domain: "todelete.example.com", appName: "todelete", app: app},
|
||||||
|
})
|
||||||
|
|
||||||
|
item := unstructured.Unstructured{}
|
||||||
|
item.SetNamespace("default")
|
||||||
|
item.SetName("test-ingress")
|
||||||
|
|
||||||
|
svc.updateFromItem(&item)
|
||||||
|
|
||||||
|
_, ok := svc.getByDomain("todelete.example.com")
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
svc := &KubernetesService{
|
||||||
|
ingressApps: make(map[ingressKey][]ingressApp),
|
||||||
|
domainIndex: make(map[string]ingressAppKey),
|
||||||
|
appNameIndex: make(map[string]ingressAppKey),
|
||||||
|
}
|
||||||
|
test.run(t, svc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@@ -18,13 +17,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/go-jose/go-jose/v4"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -120,7 +120,7 @@ type OIDCServiceConfig struct {
|
|||||||
|
|
||||||
type OIDCService struct {
|
type OIDCService struct {
|
||||||
config OIDCServiceConfig
|
config OIDCServiceConfig
|
||||||
queries *repository.Queries
|
queries repository.Store
|
||||||
clients map[string]config.OIDCClientConfig
|
clients map[string]config.OIDCClientConfig
|
||||||
privateKey *rsa.PrivateKey
|
privateKey *rsa.PrivateKey
|
||||||
publicKey crypto.PublicKey
|
publicKey crypto.PublicKey
|
||||||
@@ -128,7 +128,7 @@ type OIDCService struct {
|
|||||||
isConfigured bool
|
isConfigured bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOIDCService(config OIDCServiceConfig, queries *repository.Queries) *OIDCService {
|
func NewOIDCService(config OIDCServiceConfig, queries repository.Store) *OIDCService {
|
||||||
return &OIDCService{
|
return &OIDCService{
|
||||||
config: config,
|
config: config,
|
||||||
queries: queries,
|
queries: queries,
|
||||||
@@ -419,7 +419,7 @@ func (service *OIDCService) GetCodeEntry(c *gin.Context, codeHash string, client
|
|||||||
oidcCode, err := service.queries.GetOidcCode(c, codeHash)
|
oidcCode, err := service.queries.GetOidcCode(c, codeHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
return repository.OidcCode{}, ErrCodeNotFound
|
return repository.OidcCode{}, ErrCodeNotFound
|
||||||
}
|
}
|
||||||
return repository.OidcCode{}, err
|
return repository.OidcCode{}, err
|
||||||
@@ -529,7 +529,7 @@ func (service *OIDCService) GenerateAccessToken(c *gin.Context, client config.OI
|
|||||||
tokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
tokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
||||||
|
|
||||||
// Refresh token lives double the time of an access token but can't be used to access userinfo
|
// Refresh token lives double the time of an access token but can't be used to access userinfo
|
||||||
refrshTokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry*2) * time.Second).Unix()
|
refreshTokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry*2) * time.Second).Unix()
|
||||||
|
|
||||||
tokenResponse := TokenResponse{
|
tokenResponse := TokenResponse{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
@@ -547,7 +547,7 @@ func (service *OIDCService) GenerateAccessToken(c *gin.Context, client config.OI
|
|||||||
ClientID: client.ClientID,
|
ClientID: client.ClientID,
|
||||||
Scope: codeEntry.Scope,
|
Scope: codeEntry.Scope,
|
||||||
TokenExpiresAt: tokenExpiresAt,
|
TokenExpiresAt: tokenExpiresAt,
|
||||||
RefreshTokenExpiresAt: refrshTokenExpiresAt,
|
RefreshTokenExpiresAt: refreshTokenExpiresAt,
|
||||||
Nonce: codeEntry.Nonce,
|
Nonce: codeEntry.Nonce,
|
||||||
CodeHash: codeEntry.CodeHash,
|
CodeHash: codeEntry.CodeHash,
|
||||||
})
|
})
|
||||||
@@ -563,7 +563,7 @@ func (service *OIDCService) RefreshAccessToken(c *gin.Context, refreshToken stri
|
|||||||
entry, err := service.queries.GetOidcTokenByRefreshToken(c, service.Hash(refreshToken))
|
entry, err := service.queries.GetOidcTokenByRefreshToken(c, service.Hash(refreshToken))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
return TokenResponse{}, ErrTokenNotFound
|
return TokenResponse{}, ErrTokenNotFound
|
||||||
}
|
}
|
||||||
return TokenResponse{}, err
|
return TokenResponse{}, err
|
||||||
@@ -596,7 +596,7 @@ func (service *OIDCService) RefreshAccessToken(c *gin.Context, refreshToken stri
|
|||||||
newRefreshToken := utils.GenerateString(32)
|
newRefreshToken := utils.GenerateString(32)
|
||||||
|
|
||||||
tokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
tokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
||||||
refrshTokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry*2) * time.Second).Unix()
|
refreshTokenExpiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry*2) * time.Second).Unix()
|
||||||
|
|
||||||
tokenResponse := TokenResponse{
|
tokenResponse := TokenResponse{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
@@ -611,7 +611,7 @@ func (service *OIDCService) RefreshAccessToken(c *gin.Context, refreshToken stri
|
|||||||
AccessTokenHash: service.Hash(accessToken),
|
AccessTokenHash: service.Hash(accessToken),
|
||||||
RefreshTokenHash: service.Hash(newRefreshToken),
|
RefreshTokenHash: service.Hash(newRefreshToken),
|
||||||
TokenExpiresAt: tokenExpiresAt,
|
TokenExpiresAt: tokenExpiresAt,
|
||||||
RefreshTokenExpiresAt: refrshTokenExpiresAt,
|
RefreshTokenExpiresAt: refreshTokenExpiresAt,
|
||||||
RefreshTokenHash_2: service.Hash(refreshToken), // that's the selector, it's not stored in the db
|
RefreshTokenHash_2: service.Hash(refreshToken), // that's the selector, it's not stored in the db
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -642,7 +642,7 @@ func (service *OIDCService) GetAccessToken(c *gin.Context, tokenHash string) (re
|
|||||||
entry, err := service.queries.GetOidcToken(c, tokenHash)
|
entry, err := service.queries.GetOidcToken(c, tokenHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
return repository.OidcToken{}, ErrTokenNotFound
|
return repository.OidcToken{}, ErrTokenNotFound
|
||||||
}
|
}
|
||||||
return repository.OidcToken{}, err
|
return repository.OidcToken{}, err
|
||||||
@@ -730,15 +730,15 @@ func (service *OIDCService) Hash(token string) string {
|
|||||||
|
|
||||||
func (service *OIDCService) DeleteOldSession(ctx context.Context, sub string) error {
|
func (service *OIDCService) DeleteOldSession(ctx context.Context, sub string) error {
|
||||||
err := service.queries.DeleteOidcCodeBySub(ctx, sub)
|
err := service.queries.DeleteOidcCodeBySub(ctx, sub)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, repository.ErrNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = service.queries.DeleteOidcTokenBySub(ctx, sub)
|
err = service.queries.DeleteOidcTokenBySub(ctx, sub)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, repository.ErrNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = service.queries.DeleteOidcUserInfo(ctx, sub)
|
err = service.queries.DeleteOidcUserInfo(ctx, sub)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, repository.ErrNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -783,7 +783,7 @@ func (service *OIDCService) Cleanup() {
|
|||||||
token, err := service.queries.GetOidcTokenBySub(ctx, expiredCode.Sub)
|
token, err := service.queries.GetOidcTokenBySub(ctx, expiredCode.Sub)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tlog.App.Warn().Err(err).Msg("Failed to get OIDC token by sub")
|
tlog.App.Warn().Err(err).Msg("Failed to get OIDC token by sub")
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
sql:
|
sql:
|
||||||
- engine: "sqlite"
|
- engine: "sqlite"
|
||||||
queries: "sql/*_queries.sql"
|
queries: "sql/sqlite/*_queries.sql"
|
||||||
schema: "sql/*_schemas.sql"
|
schema: "sql/sqlite/*_schemas.sql"
|
||||||
gen:
|
gen:
|
||||||
go:
|
go:
|
||||||
package: "repository"
|
package: "sqlite"
|
||||||
out: "internal/repository"
|
out: "internal/repository/sqlite"
|
||||||
rename:
|
rename:
|
||||||
uuid: "UUID"
|
uuid: "UUID"
|
||||||
oauth_groups: "OAuthGroups"
|
oauth_groups: "OAuthGroups"
|
||||||
|
|||||||
Reference in New Issue
Block a user