Compare commits

..

21 Commits

Author SHA1 Message Date
f3182c1258 Use previous version of powerdns image, newer version is broken 2025-01-07 11:19:15 +10:00
9d3c06dbe4 Support containerized cpu limits 2024-12-02 08:44:28 +10:00
152b7666d8 New lint rules 2024-11-21 19:07:36 +10:00
4e6d65645f Fix cypress oauth test one more time 2024-11-13 14:21:16 +10:00
8434b9fce4 Fix unit test 2024-11-13 13:50:44 +10:00
f4bd65dd2c Fix oauth cypress test 2024-11-13 13:38:33 +10:00
a856c4d6e1 Use new instance of autentik db made in dev 2024-11-13 12:46:43 +10:00
2f334b5f9f Fix ci clearing users not clearing auth 2024-11-13 12:38:59 +10:00
2145df0dfb Use docker healthcheck for authentik 2024-11-13 10:28:02 +10:00
331c761a1c Wait for authentik to start in ci test 2024-11-13 08:51:24 +10:00
03b3b6379b Fix incorrect authentikm url in cypress test 2024-11-13 08:24:55 +10:00
050c087a51 Fix annoying joqqueue issue where jobs were blocked by unused channel 2024-11-13 08:03:10 +10:00
61c41b8ec3 Fixes for hanging jobs, more verbose ci output 2024-11-12 15:36:38 +10:00
47d7896301 Cypress tests for OAuth login 2024-11-12 10:38:56 +10:00
d048d1fc98 Back to go1.23 now that the bug is fixed 2024-11-08 19:48:20 +10:00
3774a40498 Add more unit tests 2024-11-07 13:00:07 +10:00
208037946f Oauth2 support 2024-11-07 07:24:38 +10:00
f23299f793 Downgrade to go 1.22 due to golang/go#68976 2024-11-04 22:54:23 +10:00
aa15052eea Fix multibuild 2024-11-04 14:40:37 +10:00
c556b8acef Cypress test for LDAP 2024-11-04 11:08:20 +10:00
4d60876394 Update cypress test suite 2024-11-04 07:59:06 +10:00
129 changed files with 2402 additions and 751 deletions

View File

@ -1,63 +1,166 @@
---
linters:
enable:
- bodyclose
- errcheck
- gosimple
- govet
- gosec
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- ineffassign
- misspell
- nakedret
- prealloc
#- revive
- staticcheck
- typecheck
- unused
- unconvert
- unparam
enable:
# Prevents against memory leaks in production caused by not closing
# file handle
- bodyclose
# Detects cloned code. DRY is good programming practice. Can cause issues
# with testing code where simplicity is preferred over duplication.
# Disabled for test code.
# - dupl
# Detects unchecked errors in go programs. These unchecked errors can be
# critical bugs in some cases.
- errcheck
# Simplifies go code.
- gosimple
# Controls Go package import order and makes it always deterministic.
- gci
# Reports suspicious constructs, maintained by goteam. e.g. Printf unused
# params not caught at compile time.
- govet
# Detect security issues with gocode. Use of secrets in code or obsolete
# security algorithms. It's imaged heuristic methods are used in finding
# problems. If issues with rules are found particular rules can be disabled
# as required. Could possibility cause issues with testing.
# Disabled for test code.
- gosec
# Detect repeated strings that could be replaced by a constant
- goconst
# Misc linters missing from other projects. Grouped into 3 categories
# diagnostics, style and performance
- gocritic
# Limits code cyclomatic complexity
- gocyclo
# Detects if code needs to be gofmt'd
- gofmt
# Detects unused go package imports
- goimports
# Detects style mistakes not correctness. Golint is meant to carry out the
# stylistic conventions put forth in Effective Go and CodeReviewComments.
# golint has false positives and false negatives and can be tweaked.
- revive
# Detects ineffectual assignments in code
- ineffassign
# Reports long lines
# - lll
# Detect commonly misspelled english words in comments
- misspell
# Detect naked returns on non-trivial functions, and conform with
# Go CodeReviewComments
- nakedret
# Detect slice allocations that can be preallocated
- prealloc
# Misc collection of static analysis tools
- staticcheck
# Detects unused struct fields
# - structcheck
# Parses and typechecks the code like the go compiler
- typecheck
# Detects unused constants, variables, functions and types
- unused
# Remove unnecessary type conversions
- unconvert
# Remove unnecessary(unused) function parameters
- unparam
linters-settings:
gosec:
excludes:
- G115
errcheck:
exclude-functions:
- fmt.Fprint
- fmt.Fprintf
goconst:
# minimal length of string constant
# default: 3
min-len: 2
# minimum number of occurrences of string constant
# default: 3
min-occurences: 2
misspell:
locale: UK
ignore-words:
- color
errcheck:
exclude-functions:
- fmt.Fprint
- fmt.Fprintf
gci:
sections:
- standard # Standard section: captures all standard packages.
- localmodule # Local module section: contains all local packages.
# - prefix(gogs.jc21.com) # Prefixed gerrit.lan packages (jumgo).
- default # Everything else (github.com, golang.org, etc).
- blank # Blank section: contains all blank imports.
custom-order: true
goconst:
# minimal length of string constant
# default: 3
min-len: 2
# minimum number of occurrences of string constant
# default: 3
min-occurences: 2
revive:
enable-all-rules: true
rules:
- name: unchecked-type-assertion
disabled: true
# handled by goconst
- name: add-constant
disabled: true
# cant limit this arbitrarily
- name: argument-limit
disabled: true
# handled by gocyclo
- name: cognitive-complexity
disabled: true
# false positive for Exported vs non-exported functions of the same name
- name: confusing-naming
disabled: true
# false positives for "" - which is the nil value of a string (also 0)
- name: confusing-results
disabled: true
# handled by gocyclo
- name: cyclomatic
disabled: true
# have comments on exported functions but not on vars/types/constants
- name: exported
arguments:
- "disableChecksOnConstants"
- "disableChecksOnTypes"
- "disableChecksOnVariables"
# false positives on bool params
- name: flag-parameter
disabled: true
# extreme verticalization can happen
- name: function-length
disabled: true
# can false positive for non-getters
- name: get-return
disabled: true
# only allows lowercase names
- name: import-alias-naming
disabled: true
# handled by lll
- name: line-length-limit
disabled: true
# don't want to arbitrarily limit this
# many places have specific model.go files to contain all structs
- name: max-public-structs
disabled: true
# disable package-comments
- name: package-comments
disabled: true
# this is handled by errcheck
- name: unhandled-error
disabled: true
- name: function-result-limit
disabled: true
issues:
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
# We have chosen an arbitrary value that works based on practical usage.
max-same: 20
# See cmdline flag documentation for more info about default excludes --exclude-use-default
# Nothing is excluded by default
exclude-use-default: false
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files. # TODO: Add examples why this is good
- path: _test\.go
linters:
# Tests should be simple? Add example why this is good?
- gocyclo
# Error checking adds verbosity and complexity for minimal value
- errcheck
# Table test encourage duplication in defining the table tests.
- dupl
# Hard coded example tokens, SQL injection and other bad practices may
# want to be tested
- gosec
# Maximum count of issues with the same text. Set to 0 to disable. Default
# is 3. We have chosen an arbitrary value that works based on practical usage.
max-same: 20
# See cmdline flag documentation for more info about default excludes
# --exclude-use-default. Nothing is excluded by default
exclude-use-default: false
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files.
# TODO: Add examples why this is good
- path: _test\.go
linters:
# Tests should be simple? Add example why this is good?
- gocyclo
# Error checking adds verbosity and complexity for minimal value
- errcheck
# Table test encourage duplication in defining the table tests.
- dupl
# Hard coded example tokens, SQL injection and other bad practices may
# want to be tested
- gosec
# Test data can long
# - lll
run:
go: '1.23'

View File

@ -18,4 +18,4 @@ threshold:
# package: 30
# (optional; default 0)
# The minimum total coverage project should have
total: 33
total: 30

View File

@ -15,6 +15,9 @@ import (
"npm/internal/jobqueue"
"npm/internal/jwt"
"npm/internal/logger"
// properly respect available cpu cores
_ "go.uber.org/automaxprocs"
)
var commit string

View File

@ -7,7 +7,7 @@
"enum": [
"local",
"ldap",
"oidc"
"oauth"
]
}
}

View File

@ -24,7 +24,7 @@
},
"type": {
"type": "string",
"pattern": "^(local|ldap|oidc)$"
"pattern": "^(local|ldap|oauth)$"
}
}
}

View File

@ -53,7 +53,7 @@
},
"type": {
"type": "string",
"pattern": "^(local|ldap|oidc)$"
"pattern": "^(local|ldap|oauth)$"
}
}
},

View File

@ -48,8 +48,8 @@ INSERT INTO `setting` (
(
ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000),
ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000),
"oidc-auth",
"Configuration for OIDC authentication",
"oauth-auth",
"Configuration for OAuth authentication",
'{}' -- remember this is json
),
(

View File

@ -48,8 +48,8 @@ INSERT INTO "setting" (
(
EXTRACT(EPOCH FROM TIMESTAMP '2011-05-17 10:40:28.876944') * 1000,
EXTRACT(EPOCH FROM TIMESTAMP '2011-05-17 10:40:28.876944') * 1000,
'oidc-auth',
'Configuration for OIDC authentication',
'oauth-auth',
'Configuration for OAuth authentication',
'{}' -- remember this is json
),
(

View File

@ -47,8 +47,8 @@ INSERT INTO `setting` (
(
unixepoch() * 1000,
unixepoch() * 1000,
"oidc-auth",
"Configuration for OIDC authentication",
"oauth-auth",
"Configuration for OAuth authentication",
'{}' -- remember this is json
),
(

View File

@ -9,11 +9,12 @@ require (
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e
github.com/fatih/color v1.17.0
github.com/fatih/color v1.18.0
github.com/glebarez/sqlite v1.11.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1
github.com/go-chi/jwtauth/v5 v5.3.1
github.com/go-ldap/ldap/v3 v3.4.8
github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec
github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760
github.com/patrickmn/go-cache v2.1.0+incompatible
@ -21,12 +22,14 @@ require (
github.com/rotisserie/eris v0.5.4
github.com/stretchr/testify v1.9.0
github.com/vrischmann/envconfig v1.3.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.27.0
gorm.io/datatypes v1.2.1
golang.org/x/crypto v0.29.0
golang.org/x/oauth2 v0.24.0
gorm.io/datatypes v1.2.4
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.11
gorm.io/gorm v1.25.12
gorm.io/plugin/soft_delete v1.2.1
)
@ -35,55 +38,57 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-ldap/ldap/v3 v3.4.8 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jspointer v0.0.0-20181205001929-82fadba7561c // indirect
github.com/lestrrat-go/jwx/v2 v2.0.20 // indirect
github.com/lestrrat-go/jwx/v2 v2.1.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lestrrat-go/pdebug/v3 v3.0.1 // indirect
github.com/lestrrat-go/structinfo v0.0.0-20190212233437-acd51874663b // indirect
github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.17.0 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
modernc.org/sqlite v1.28.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)
replace github.com/amacneil/dbmate/v2 => github.com/jc21/dbmate/v2 v2.0.0-20230527023241-0aaa124cc0f1

View File

@ -4,6 +4,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
@ -15,22 +16,23 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e h1:2R8DvYLNr5DL25eWwpOdPno1eIbTNjJC0d7v8ti5cus=
github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e/go.mod h1:YjikoytuRI4q+GRd3xrOrKJN+Ayi2dwRomHLDDeMHfs=
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/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@ -42,8 +44,8 @@ github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -52,33 +54,38 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jc21/dbmate/v2 v2.0.0-20230527023241-0aaa124cc0f1 h1:WEZwsDG5eXdgh0NfA9SpuShOP6rJCah22ihvZsaoimM=
github.com/jc21/dbmate/v2 v2.0.0-20230527023241-0aaa124cc0f1/go.mod h1:XAPcHsokw7nyvFbuN9FdcYb8JSEUTaJDFYOirnNxEvc=
github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec h1:KKntwkZlM2w/88QiDyAeZ4th8grqtituzMW8qyapYzc=
github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec/go.mod h1:4v5Xmm0eYuaWqKJ63XUV5YfQPoxtId3DgDytbnWhi+s=
github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760 h1:7wxq2DIgtO36KLrFz1RldysO0WVvcYsD49G9tyAs01k=
github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760/go.mod h1:yIq2t51OJgVsdRlPY68NAnyVdBH0kYXxDTFtUxOap80=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
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/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@ -101,21 +108,22 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jspointer v0.0.0-20181205001929-82fadba7561c h1:pGh5EFIfczeDHwgMHgfwjhZzL+8/E3uZF6T7vER/W8c=
github.com/lestrrat-go/jspointer v0.0.0-20181205001929-82fadba7561c/go.mod h1:xw2Gm4Mg+ST9s8fHR1VkUIyOJMJnSloRZlPQB+wyVpY=
github.com/lestrrat-go/jwx/v2 v2.0.20 h1:sAgXuWS/t8ykxS9Bi2Qtn5Qhpakw1wrcjxChudjolCc=
github.com/lestrrat-go/jwx/v2 v2.0.20/go.mod h1:UlCSmKqw+agm5BsOBfEAbTvKsEApaGNqHAEUTv5PJC4=
github.com/lestrrat-go/jwx/v2 v2.1.2 h1:6poete4MPsO8+LAEVhpdrNI4Xp2xdiafgl2RD89moBc=
github.com/lestrrat-go/jwx/v2 v2.1.2/go.mod h1:pO+Gz9whn7MPdbsqSJzG8TlEpMZCwQDXnFJ+zsUVh8Y=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8=
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
github.com/lestrrat-go/structinfo v0.0.0-20190212233437-acd51874663b h1:YUFRoeHK/mvRjBR0bBRDC7ZGygYchoQ8j1xMENlObro=
github.com/lestrrat-go/structinfo v0.0.0-20190212233437-acd51874663b/go.mod h1:s2U6PowV3/Jobkx/S9d0XiPwOzs6niW3DIouw+7nZC8=
github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb h1:DDg5u5lk2v8O8qxs8ecQkMUBj3tLW6wkSLzxxOyi1Ig=
github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb/go.mod h1:i+E8Uf04vf2QjOWyJdGY75vmG+4rxiZW2kIj1lTB5mo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -131,6 +139,8 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@ -138,12 +148,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
@ -157,6 +168,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -175,6 +188,8 @@ github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJ
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4=
gitlab.com/jc21com/sqlite v1.22.2-0.20230527022643-b56cedb3bc85 h1:NPHauobrOymc80Euu+e0tsMyXcdtLCX5bQPKX5zsI38=
gitlab.com/jc21com/sqlite v1.22.2-0.20230527022643-b56cedb3bc85/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -182,12 +197,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@ -197,11 +214,15 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -216,8 +237,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -230,14 +251,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -250,8 +271,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.1 h1:r+g0bk4LPCW2v4+Ls7aeNgGme7JYdNDQ2VtvlNUfBh0=
gorm.io/datatypes v1.2.1/go.mod h1:hYK6OTb/1x+m96PgoZZq10UXJ6RvEBb9kRDQ2yyhzGs=
gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4=
gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
@ -264,33 +285,43 @@ gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHD
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA=
modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=

View File

@ -3,17 +3,17 @@ package handler
import (
"encoding/json"
"net/http"
h "npm/internal/api/http"
"npm/internal/errors"
"npm/internal/logger"
"slices"
"time"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/entity/auth"
"npm/internal/entity/setting"
"npm/internal/entity/user"
"npm/internal/errors"
njwt "npm/internal/jwt"
"npm/internal/logger"
"gorm.io/gorm"
)
@ -70,8 +70,6 @@ func NewToken() func(http.ResponseWriter, *http.Request) {
switch payload.Type {
case "ldap":
newTokenLDAP(w, r, payload)
case "oidc":
newTokenOIDC(w, r, payload)
case "local":
newTokenLocal(w, r, payload)
}
@ -199,10 +197,6 @@ func newTokenLDAP(w http.ResponseWriter, r *http.Request, payload tokenPayload)
}
}
func newTokenOIDC(w http.ResponseWriter, r *http.Request, _ tokenPayload) {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "NOT YET SUPPORTED", nil)
}
// RefreshToken an existing token by given them a new one with the same claims
// Route: POST /auth/refresh
func RefreshToken() func(http.ResponseWriter, *http.Request) {

View File

@ -151,7 +151,7 @@ func getCertificateFromRequest(w http.ResponseWriter, r *http.Request) *certific
// fillObjectFromBody has some reusable code for all endpoints that
// have a certificate id in the url. it will write errors to the output.
func fillObjectFromBody(w http.ResponseWriter, r *http.Request, validationSchema string, o interface{}) bool {
func fillObjectFromBody(w http.ResponseWriter, r *http.Request, validationSchema string, o any) bool {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
if validationSchema != "" {
@ -176,10 +176,10 @@ func fillObjectFromBody(w http.ResponseWriter, r *http.Request, validationSchema
return true
}
func configureCertificate(c certificate.Model) {
func configureCertificate(cert certificate.Model) {
err := jobqueue.AddJob(jobqueue.Job{
Name: "RequestCertificate",
Action: c.Request,
Action: cert.Request,
})
if err != nil {
logger.Error("ConfigureCertificateError", err)

View File

@ -2,6 +2,7 @@ package handler
import (
"net/http"
h "npm/internal/api/http"
"npm/internal/config"
)

View File

@ -2,6 +2,7 @@ package handler
import (
"net/http"
"npm/internal/acme"
h "npm/internal/api/http"
"npm/internal/config"

View File

@ -21,11 +21,22 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
return pageInfo, err
}
// pageInfo.Sort = middleware.GetSortFromContext(r)
return pageInfo, nil
}
func getQueryVarString(r *http.Request, varName string, required bool, defaultValue string) (string, error) {
queryValues := r.URL.Query()
varValue := queryValues.Get(varName)
if varValue == "" && required {
return "", eris.Errorf("%v was not supplied in the request", varName)
} else if varValue == "" {
return defaultValue, nil
}
return varValue, nil
}
func getQueryVarInt(r *http.Request, varName string, required bool, defaultValue int) (int, error) {
queryValues := r.URL.Query()
varValue := queryValues.Get(varName)
@ -45,7 +56,7 @@ func getQueryVarInt(r *http.Request, varName string, required bool, defaultValue
}
func getURLParamInt(r *http.Request, varName string) (uint, error) {
var defaultValue uint = 0
var defaultValue uint
required := true
paramStr := chi.URLParam(r, varName)

View File

@ -3,9 +3,10 @@ package handler
import (
"net/http"
"net/http/httptest"
"npm/internal/model"
"testing"
"npm/internal/model"
"github.com/stretchr/testify/assert"
)

View File

@ -192,7 +192,7 @@ func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request)
return
}
if format == "text" {
h.ResultResponseText(w, r, http.StatusOK, content)
h.ResultResponseText(w, http.StatusOK, content)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, content)
@ -202,11 +202,11 @@ func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request)
}
}
func configureHost(h host.Model) {
func configureHost(hst host.Model) {
err := jobqueue.AddJob(jobqueue.Job{
Name: "NginxConfigureHost",
Action: func() error {
return nginx.ConfigureHost(h)
return nginx.ConfigureHost(hst)
},
})
if err != nil {

View File

@ -0,0 +1,156 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
h "npm/internal/api/http"
"npm/internal/entity/auth"
"npm/internal/entity/setting"
"npm/internal/entity/user"
"npm/internal/errors"
njwt "npm/internal/jwt"
"npm/internal/logger"
"gorm.io/gorm"
)
// getRequestIPAddress will use X-FORWARDED-FOR header if it exists
// otherwise it will use RemoteAddr
func getRequestIPAddress(r *http.Request) string {
// this Get is case insensitive
xff := r.Header.Get("X-FORWARDED-FOR")
if xff != "" {
ip, _, _ := strings.Cut(xff, ",")
return strings.TrimSpace(ip)
}
return r.RemoteAddr
}
// OAuthLogin ...
// Route: GET /oauth/login
func OAuthLogin() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if !setting.AuthMethodEnabled(auth.TypeOAuth) {
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
return
}
redirectBase, _ := getQueryVarString(r, "redirect_base", false, "")
url, err := auth.OAuthLogin(redirectBase, getRequestIPAddress(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, url)
}
}
// OAuthRedirect ...
// Route: GET /oauth/redirect
func OAuthRedirect() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if !setting.AuthMethodEnabled(auth.TypeOAuth) {
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
return
}
code, err := getQueryVarString(r, "code", true, "")
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
ou, err := auth.OAuthReturn(r.Context(), code, getRequestIPAddress(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
if ou.Identifier == "" {
h.ResultErrorJSON(w, r, http.StatusBadRequest, "User found, but OAuth identifier seems misconfigured", nil)
return
}
jwt, err := newTokenOAuth(ou)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
return
}
// encode jwt to json
j, _ := json.Marshal(jwt)
// Redirect to frontend with success
http.Redirect(w, r, fmt.Sprintf("/?token_response=%s", url.QueryEscape(string(j))), http.StatusSeeOther)
}
}
// newTokenOAuth takes a OAuthUser and creates a new token,
// optionally creating a new user if one does not exist
func newTokenOAuth(ou *auth.OAuthUser) (*njwt.GeneratedResponse, error) {
// Get OAuth settings
oAuthSettings, err := setting.GetOAuthSettings()
if err != nil {
logger.Error("OAuth settings not found", err)
return nil, err
}
// Get Auth by identity
authObj, authErr := auth.GetByIdenityType(ou.GetID(), auth.TypeOAuth)
if authErr == gorm.ErrRecordNotFound {
// Auth is not found for this identity. We can create it
if !oAuthSettings.AutoCreateUser {
// user does not have an auth record
// and auto create is disabled. Showing account disabled error
// for the time being
return nil, errors.ErrUserDisabled
}
// Attempt to find user by email
foundUser, err := user.GetByEmail(ou.GetEmail())
if err == gorm.ErrRecordNotFound {
// User not found, create user
foundUser, err = user.CreateFromOAuthUser(ou)
if err != nil {
logger.Error("user.CreateFromOAuthUser", err)
return nil, err
}
logger.Info("Created user from OAuth: %s, %s", ou.GetID(), foundUser.Email)
} else if err != nil {
logger.Error("user.GetByEmail", err)
return nil, err
}
// Create auth record and attach to this user
authObj = auth.Model{
UserID: foundUser.ID,
Type: auth.TypeOAuth,
Identity: ou.GetID(),
}
if err := authObj.Save(); err != nil {
logger.Error("auth.Save", err)
return nil, err
}
logger.Info("Created OAuth auth for user: %s, %s", ou.GetID(), foundUser.Email)
} else if authErr != nil {
logger.Error("auth.GetByIdenityType", err)
return nil, authErr
}
userObj, userErr := user.GetByID(authObj.UserID)
if userErr != nil {
return nil, userErr
}
if userObj.IsDisabled {
return nil, errors.ErrUserDisabled
}
jwt, err := njwt.Generate(&userObj, false)
return &jwt, err
}

View File

@ -12,7 +12,7 @@ import (
"npm/internal/config"
"npm/internal/logger"
jsref "github.com/jc21/jsref"
"github.com/jc21/jsref"
"github.com/jc21/jsref/provider"
)
@ -24,7 +24,7 @@ var (
// Schema simply reads the swagger schema from disk and returns is raw
// Route: GET /schema
func Schema() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(getSchema()))
@ -42,8 +42,8 @@ func getSchema() []byte {
swaggerSchema = []byte(strings.ReplaceAll(string(swaggerSchema), "{{VERSION}}", config.Version))
// Dereference the JSON Schema:
var schema interface{}
if err := json.Unmarshal(swaggerSchema, &schema); err != nil {
var sch any
if err := json.Unmarshal(swaggerSchema, &sch); err != nil {
logger.Error("SwaggerUnmarshalError", err)
return nil
}
@ -55,7 +55,7 @@ func getSchema() []byte {
logger.Error("SchemaProviderError", err)
}
result, err := resolver.Resolve(schema, "", []jsref.Option{jsref.WithRecursiveResolution(true)}...)
result, err := resolver.Resolve(sch, "", []jsref.Option{jsref.WithRecursiveResolution(true)}...)
if err != nil {
logger.Error("SwaggerResolveError", err)
} else {

View File

@ -95,7 +95,7 @@ func CreateUpstream() func(http.ResponseWriter, *http.Request) {
}
}
// UpdateHost updates a host
// UpdateUpstream updates a stream
// Route: PUT /upstreams/{upstreamID}
func UpdateUpstream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
@ -167,7 +167,7 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) {
}
}
// GetHostNginxConfig will return a Host's nginx config from disk
// GetUpstreamNginxConfig will return a Host's nginx config from disk
// Route: GET /upstreams/{upstreamID}/nginx-config
// Route: GET /upstreams/{upstreamID}/nginx-config.txt
func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) {
@ -191,7 +191,7 @@ func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Reque
return
}
if format == "text" {
h.ResultResponseText(w, r, http.StatusOK, content)
h.ResultResponseText(w, http.StatusOK, content)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, content)

View File

@ -21,19 +21,19 @@ var (
// Response interface for standard API results
type Response struct {
Result interface{} `json:"result"`
Error interface{} `json:"error,omitempty"`
Result any `json:"result"`
Error any `json:"error,omitempty"`
}
// ErrorResponse interface for errors returned via the API
type ErrorResponse struct {
Code interface{} `json:"code"`
Message interface{} `json:"message"`
Invalid interface{} `json:"invalid,omitempty"`
Code any `json:"code"`
Message any `json:"message"`
Invalid any `json:"invalid,omitempty"`
}
// ResultResponseJSON will write the result as json to the http output
func ResultResponseJSON(w http.ResponseWriter, r *http.Request, status int, result interface{}) {
func ResultResponseJSON(w http.ResponseWriter, r *http.Request, status int, result any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
@ -77,7 +77,7 @@ func ResultSchemaErrorJSON(w http.ResponseWriter, r *http.Request, errs []jsonsc
}
// ResultErrorJSON will format the result as a standard error object and send it for output
func ResultErrorJSON(w http.ResponseWriter, r *http.Request, status int, message string, extended interface{}) {
func ResultErrorJSON(w http.ResponseWriter, r *http.Request, status int, message string, extended any) {
errorResponse := ErrorResponse{
Code: status,
Message: message,
@ -98,7 +98,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
}
// ResultResponseText will write the result as text to the http output
func ResultResponseText(w http.ResponseWriter, r *http.Request, status int, content string) {
func ResultResponseText(w http.ResponseWriter, status int, content string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(status)
fmt.Fprint(w, content)

View File

@ -21,7 +21,7 @@ func TestResultResponseJSON(t *testing.T) {
tests := []struct {
name string
status int
given interface{}
given any
want string
}{
{
@ -34,9 +34,9 @@ func TestResultResponseJSON(t *testing.T) {
name: "detailed response",
status: http.StatusBadRequest,
given: user.Model{
ModelBase: model.ModelBase{ID: 10},
Email: "me@example.com",
Name: "John Doe",
Base: model.Base{ID: 10},
Email: "me@example.com",
Name: "John Doe",
},
want: "{\"result\":{\"id\":10,\"created_at\":0,\"updated_at\":0,\"name\":\"John Doe\",\"email\":\"me@example.com\",\"is_disabled\":false,\"gravatar_url\":\"\"}}",
},
@ -118,7 +118,7 @@ func TestResultErrorJSON(t *testing.T) {
name string
status int
message string
extended interface{}
extended any
want string
}{
{
@ -180,9 +180,8 @@ func TestResultResponseText(t *testing.T) {
defer goleak.VerifyNone(t, goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
t.Run("basic test", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/anything", nil)
w := httptest.NewRecorder()
ResultResponseText(w, r, http.StatusOK, "omg this works")
ResultResponseText(w, http.StatusOK, "omg this works")
res := w.Result()
defer res.Body.Close()
body, err := io.ReadAll(res.Body)

View File

@ -15,7 +15,7 @@ func TestAccessControl(t *testing.T) {
// goleak is used to detect goroutine leaks
defer goleak.VerifyNone(t, goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})

View File

@ -18,6 +18,6 @@ func AuthCacheInit() {
}
// AuthCacheSet will store the item in memory for the expiration time
func AuthCacheSet(k string, x interface{}) {
func AuthCacheSet(k string, x any) {
AuthCache.Set(k, x, cache.DefaultExpiration)
}

View File

@ -26,7 +26,7 @@ func TestBodyContext(t *testing.T) {
rr := httptest.NewRecorder()
// Create a test handler that checks the context for the body data
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
bodyData := r.Context().Value(c.BodyCtxKey).([]byte)
assert.Equal(t, body, bodyData)
})

View File

@ -15,7 +15,7 @@ func TestCors(t *testing.T) {
r := chi.NewRouter()
r.Use(middleware.Cors(r))
r.Get("/test", func(w http.ResponseWriter, r *http.Request) {
r.Get("/test", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("test"))
})
@ -48,7 +48,7 @@ func TestOptions(t *testing.T) {
r := chi.NewRouter()
r.Use(middleware.Options(r))
r.Get("/test", func(w http.ResponseWriter, r *http.Request) {
r.Get("/test", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("test"))
})

View File

@ -5,11 +5,11 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
"npm/internal/api/middleware"
"npm/internal/config"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
func TestEnforceSetup(t *testing.T) {
@ -37,7 +37,7 @@ func TestEnforceSetup(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
config.IsSetup = tt.isSetup
handler := middleware.EnforceSetup()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := middleware.EnforceSetup()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))

View File

@ -23,7 +23,7 @@ func TestExpansion(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
expand := middleware.GetExpandFromContext(r)
assert.Equal(t, []string{"item1", "item2"}, expand)
})
@ -39,7 +39,7 @@ func TestExpansion(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
expand := middleware.GetExpandFromContext(r)
assert.Nil(t, expand)
})

View File

@ -21,7 +21,7 @@ import (
// and the sort parameter is valid as well.
// After we have determined what the Filters are to be, they are saved on the Context
// to be used later in other endpoints.
func ListQuery(obj interface{}) func(http.Handler) http.Handler {
func ListQuery(obj any) func(http.Handler) http.Handler {
schemaData := tags.GetFilterSchema(obj)
filterMap := tags.GetFilterMap(obj, "")
@ -29,13 +29,13 @@ func ListQuery(obj interface{}) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, statusCode, errMsg, errors := listQueryFilters(r, ctx, schemaData)
ctx, statusCode, errMsg, errors := listQueryFilters(ctx, r, schemaData)
if statusCode > 0 {
h.ResultErrorJSON(w, r, statusCode, errMsg, errors)
return
}
ctx, statusCode, errMsg = listQuerySort(r, filterMap, ctx)
ctx, statusCode, errMsg = listQuerySort(ctx, r, filterMap)
if statusCode > 0 {
h.ResultErrorJSON(w, r, statusCode, errMsg, nil)
return
@ -47,9 +47,9 @@ func ListQuery(obj interface{}) func(http.Handler) http.Handler {
}
func listQuerySort(
ctx context.Context,
r *http.Request,
filterMap map[string]model.FilterMapValue,
ctx context.Context,
) (context.Context, int, string) {
var sortFields []model.Sort
@ -99,10 +99,10 @@ func listQuerySort(
}
func listQueryFilters(
r *http.Request,
ctx context.Context,
r *http.Request,
schemaData string,
) (context.Context, int, string, interface{}) {
) (context.Context, int, string, any) {
reservedFilterKeys := []string{
"limit",
"offset",

View File

@ -53,7 +53,7 @@ func TestListQuery(t *testing.T) {
ctx = context.WithValue(ctx, c.FiltersCtxKey, tags.GetFilterSchema(testObj))
rr := httptest.NewRecorder()
handler := middleware.ListQuery(testObj)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := middleware.ListQuery(testObj)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))

View File

@ -0,0 +1,16 @@
package middleware
import (
"net/http"
"npm/internal/logger"
)
// Log will print out route information to the logger
// only when debug is enabled
func Log(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Debug("Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}

View File

@ -33,7 +33,6 @@ func CheckRequestSchema(ctx context.Context, schemaData string, payload []byte)
func EnforceRequestSchema(schemaData string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get content from context
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)

View File

@ -29,7 +29,7 @@ import (
// NewRouter returns a new router object
func NewRouter() http.Handler {
// Cors
cors := cors.New(cors.Options{
corss := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-Requested-With"},
@ -42,7 +42,7 @@ func NewRouter() http.Handler {
middleware.AccessControl,
middleware.Cors(r),
middleware.Options(r),
cors.Handler,
corss.Handler,
chiMiddleware.RealIP,
chiMiddleware.Recoverer,
chiMiddleware.Throttle(5),
@ -50,6 +50,7 @@ func NewRouter() http.Handler {
middleware.Expansion,
middleware.DecodeAuth(),
middleware.BodyContext(),
middleware.Log,
)
return applyRoutes(r)
@ -61,6 +62,12 @@ func applyRoutes(r chi.Router) chi.Router {
r.NotFound(handler.NotFound())
r.MethodNotAllowed(handler.NotAllowed())
// OAuth endpoints aren't technically API endpoints
r.With(middleware.EnforceSetup()).Route("/oauth", func(r chi.Router) {
r.Get("/login", handler.OAuthLogin())
r.Get("/redirect", handler.OAuthRedirect())
})
// SSE - requires a sse token as the `jwt` get parameter
// Exists inside /api but it's here so that we can skip the Timeout middleware
// that applies to other endpoints.

View File

@ -18,7 +18,7 @@ func CreateDNSProvider() string {
allSchemasWrapped := make([]string, 0)
for providerName, provider := range allProviders {
schema, err := provider.GetJsonSchema()
schema, err := provider.GetJSONSchema()
if err != nil {
logger.Error("ProviderSchemaError", eris.Wrapf(err, "Invalid Provider Schema for %s: %v", provider.Title, err))
} else {

View File

@ -18,7 +18,7 @@ func GetToken() string {
"properties": {
"type": {
"type": "string",
"enum": ["local", "ldap", "oidc"]
"enum": ["local", "ldap"]
},
"identity": %s,
"secret": %s

View File

@ -3,9 +3,10 @@ package schema
import (
"bytes"
"encoding/json"
"npm/internal/entity/certificate"
"testing"
"npm/internal/entity/certificate"
"github.com/stretchr/testify/assert"
)

View File

@ -1,6 +1,6 @@
package schema
// UpdateHostTemplate is the schema for incoming data validation
// UpdateNginxTemplate is the schema for incoming data validation
func UpdateNginxTemplate() string {
return `
{

View File

@ -23,6 +23,7 @@ func InitArgs(version, commit *string) {
if appArguments.Version {
fmt.Printf("v%s (%s)\n", *version, *commit)
// nolint: revive
os.Exit(0)
}
}

View File

@ -2,6 +2,7 @@ package config
import (
"fmt"
"npm/internal/logger"
)

View File

@ -46,8 +46,8 @@ func SetDB(db *gorm.DB) {
func connect() (*gorm.DB, error) {
var d gorm.Dialector
dsn := config.Configuration.DB.GetGormConnectURL()
switch strings.ToLower(config.Configuration.DB.Driver) {
switch strings.ToLower(config.Configuration.DB.Driver) {
case config.DatabaseSqlite:
// autocreate(dsn)
d = sqlite.Open(dsn)

View File

@ -2,8 +2,9 @@ package database
import (
"fmt"
"npm/internal/config"
"strings"
"npm/internal/config"
)
const (

View File

@ -9,6 +9,8 @@ import (
"npm/internal/logger"
"github.com/amacneil/dbmate/v2/pkg/dbmate"
// Drivers:
_ "github.com/amacneil/dbmate/v2/pkg/driver/mysql"
_ "github.com/amacneil/dbmate/v2/pkg/driver/postgres"
_ "github.com/amacneil/dbmate/v2/pkg/driver/sqlite"

View File

@ -2,6 +2,7 @@ package dnsproviders
import (
"encoding/json"
"npm/internal/errors"
)
@ -31,8 +32,8 @@ type Provider struct {
Properties map[string]providerField `json:"properties"`
}
// GetJsonSchema encodes this object as JSON string
func (p *Provider) GetJsonSchema() (string, error) {
// GetJSONSchema encodes this object as JSON string
func (p *Provider) GetJSONSchema() (string, error) {
b, err := json.Marshal(p)
return string(b), err
}

View File

@ -1,9 +1,10 @@
package dnsproviders
import (
"npm/internal/util"
"testing"
"npm/internal/util"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
@ -13,7 +14,7 @@ func TestAcmeDNSProvider(t *testing.T) {
defer goleak.VerifyNone(t, goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
provider := getDNSAcmeDNS()
json, err := provider.GetJsonSchema()
json, err := provider.GetJSONSchema()
assert.Nil(t, err)
assert.Equal(t, `{
"title": "dns_acmedns",

View File

@ -1,9 +1,10 @@
package dnsproviders
import (
"npm/internal/util"
"testing"
"npm/internal/util"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
@ -14,7 +15,7 @@ func TestAdProvider(t *testing.T) {
provider := getDNSAd()
provider.ConvertToUpdatable()
json, err := provider.GetJsonSchema()
json, err := provider.GetJSONSchema()
assert.Nil(t, err)
assert.Equal(t, `{
"title": "dns_ad",

View File

@ -1,9 +1,10 @@
package dnsproviders
import (
"npm/internal/util"
"testing"
"npm/internal/util"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
@ -13,7 +14,7 @@ func TestAliProvider(t *testing.T) {
defer goleak.VerifyNone(t, goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
provider := getDNSAli()
json, err := provider.GetJsonSchema()
json, err := provider.GetJSONSchema()
assert.Nil(t, err)
assert.Equal(t, `{
"title": "dns_ali",

View File

@ -11,7 +11,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Name string `json:"name" gorm:"column:name" filter:"name,string"`
Meta types.JSONB `json:"meta" gorm:"column:meta"`

View File

@ -92,6 +92,21 @@ func (s *testsuite) TestGetByUserIDType() {
assertModel(s.T(), m)
}
func (s *testsuite) TestGetByIdenityType() {
// goleak is used to detect goroutine leaks
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
s.mock.
ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "auth" WHERE identity = $1 AND type = $2 AND "auth"."is_deleted" = $3 ORDER BY "auth"."id" LIMIT $4`)).
WithArgs("johndoe", TypeLocal, 0, 1).
WillReturnRows(s.singleRow)
m, err := GetByIdenityType("johndoe", TypeLocal)
require.NoError(s.T(), err)
require.NoError(s.T(), s.mock.ExpectationsWereMet())
assertModel(s.T(), m)
}
func (s *testsuite) TestSave() {
// goleak is used to detect goroutine leaks
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
@ -124,13 +139,11 @@ func (s *testsuite) TestSave() {
func (s *testsuite) TestSetPassword() {
// goleak is used to detect goroutine leaks
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
m := Model{UserID: 100}
err := m.SetPassword("abc123")
require.NoError(s.T(), err)
assert.Equal(s.T(), TypeLocal, m.Type)
assert.Greater(s.T(), len(m.Secret), 15)
}
func (s *testsuite) TestValidateSecret() {

View File

@ -70,7 +70,7 @@ func ldapSearchUser(l *ldap3.Conn, ldapSettings setting.LDAPSettings, username s
0,
false,
strings.Replace(ldapSettings.SelfFilter, "{{USERNAME}}", username, 1),
nil, // []string{"name"},
nil,
nil,
)

View File

@ -22,7 +22,7 @@ func GetByUserIDType(userID uint, authType string) (Model, error) {
return auth, result.Error
}
// GetByUserIDType finds a user by id and type
// GetByIdenityType finds a user by identity and type
func GetByIdenityType(identity string, authType string) (Model, error) {
var auth Model
db := database.GetDB()

View File

@ -12,12 +12,12 @@ import (
const (
TypeLocal = "local"
TypeLDAP = "ldap"
TypeOIDC = "oidc"
TypeOAuth = "oauth"
)
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id"`
Type string `json:"type" gorm:"column:type;default:local"`
Identity string `json:"identity,omitempty" gorm:"column:identity"`

View File

@ -0,0 +1,218 @@
package auth
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"time"
"npm/internal/entity/setting"
"npm/internal/logger"
cache "github.com/patrickmn/go-cache"
"github.com/rotisserie/eris"
"golang.org/x/oauth2"
)
// AuthCache is a cache item that stores the Admin API data for each admin that has been requesting endpoints
var (
OAuthCache *cache.Cache
settingGetOAuthSettings = setting.GetOAuthSettings
)
// OAuthCacheInit will create a new Memory Cache
func OAuthCacheInit() {
if OAuthCache == nil {
logger.Debug("Creating a new OAuthCache")
OAuthCache = cache.New(5*time.Minute, 5*time.Minute)
}
}
// OAuthUser is the OAuth User
type OAuthUser struct {
Identifier string `json:"identifier"`
Token string `json:"token"`
Resource map[string]any `json:"resource"`
}
// GetResourceField will attempt to get a field from the resource
func (m *OAuthUser) GetResourceField(field string) string {
if m.Resource != nil {
if value, ok := m.Resource[field]; ok {
return value.(string)
}
}
return ""
}
// GetID attempts to get an ID from the resource
func (m *OAuthUser) GetID() string {
if m.Identifier != "" {
return m.Identifier
}
fields := []string{
"uid",
"user_id",
"username",
"preferred_username",
"email",
"mail",
}
for _, field := range fields {
if val := m.GetResourceField(field); val != "" {
return val
}
}
return ""
}
// GetName attempts to get a name from the resource
// using different fields
func (m *OAuthUser) GetName() string {
fields := []string{
"nickname",
"given_name",
"name",
"preferred_username",
"username",
}
for _, field := range fields {
if name := m.GetResourceField(field); name != "" {
return name
}
}
// Fallback:
return m.Identifier
}
// GetEmail will return an email address even if it can't be known in the
// Resource
func (m *OAuthUser) GetEmail() string {
// See if there's an email field first
if email := m.GetResourceField("email"); email != "" {
return email
}
// Return the identifier if it looks like an email
if m.Identifier != "" {
if strings.Contains(m.Identifier, "@") {
return m.Identifier
}
return fmt.Sprintf("%s@oauth", m.Identifier)
}
return ""
}
func getOAuth2Config() (*oauth2.Config, *setting.OAuthSettings, error) {
oauthSettings, err := settingGetOAuthSettings()
if err != nil {
return nil, nil, err
}
if oauthSettings.ClientID == "" || oauthSettings.ClientSecret == "" || oauthSettings.AuthURL == "" || oauthSettings.TokenURL == "" {
return nil, nil, eris.New("oauth-settings-incorrect")
}
return &oauth2.Config{
ClientID: oauthSettings.ClientID,
ClientSecret: oauthSettings.ClientSecret,
Scopes: oauthSettings.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: oauthSettings.AuthURL,
TokenURL: oauthSettings.TokenURL,
},
}, &oauthSettings, nil
}
// OAuthLogin is hit by the client to generate a URL to redirect to
// and start the oauth process
func OAuthLogin(redirectBase, ipAddress string) (string, error) {
OAuthCacheInit()
conf, _, err := getOAuth2Config()
if err != nil {
return "", err
}
verifier := oauth2.GenerateVerifier()
OAuthCache.Set(getCacheKey(ipAddress), verifier, cache.DefaultExpiration)
// todo: state should be unique to the incoming IP address of the requester, I guess
url := conf.AuthCodeURL("state", oauth2.AccessTypeOnline, oauth2.S256ChallengeOption(verifier))
if redirectBase != "" {
url = url + "&redirect_uri=" + redirectBase + "/oauth/redirect"
}
logger.Debug("URL: %s", url)
return url, nil
}
// OAuthReturn ...
func OAuthReturn(ctx context.Context, code, ipAddress string) (*OAuthUser, error) {
// Just in case...
OAuthCacheInit()
conf, oauthSettings, err := getOAuth2Config()
if err != nil {
return nil, err
}
verifier, found := OAuthCache.Get(getCacheKey(ipAddress))
if !found {
return nil, eris.New("oauth-verifier-not-found")
}
// Use the authorization code that is pushed to the redirect
// URL. Exchange will do the handshake to retrieve the
// initial access token. The HTTP Client returned by
// conf.Client will refresh the token as necessary.
tok, err := conf.Exchange(ctx, code, oauth2.VerifierOption(verifier.(string)))
if err != nil {
return nil, err
}
// At this stage, the token is the JWT as given by the oauth server.
// we need to use that to get more info about this user,
// and then we'll create our own jwt for use later.
client := conf.Client(ctx, tok)
resp, err := client.Get(oauthSettings.ResourceURL)
if err != nil {
return nil, err
}
// nolint: errcheck, gosec
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
ou := OAuthUser{
Token: tok.AccessToken,
}
// unmarshal the body into a interface
if err := json.Unmarshal(body, &ou.Resource); err != nil {
return nil, err
}
// Attempt to get the identifier from the resource
if oauthSettings.Identifier != "" {
ou.Identifier = ou.GetResourceField(oauthSettings.Identifier)
}
return &ou, nil
}
func getCacheKey(ipAddress string) string {
return fmt.Sprintf("oauth-%s", ipAddress)
}

View File

@ -0,0 +1,430 @@
package auth
import (
"context"
"testing"
"npm/internal/entity/setting"
cache "github.com/patrickmn/go-cache"
"github.com/rotisserie/eris"
"github.com/stretchr/testify/assert"
)
func TestGetOAuth2Config(t *testing.T) {
tests := []struct {
name string
mockSettings setting.OAuthSettings
expectedError error
}{
{
name: "Valid settings",
mockSettings: setting.OAuthSettings{
ClientID: "valid-client-id",
ClientSecret: "valid-client-secret",
AuthURL: "https://auth.url",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
},
expectedError: nil,
},
{
name: "Missing ClientID",
mockSettings: setting.OAuthSettings{
ClientSecret: "valid-client-secret",
AuthURL: "https://auth.url",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
},
expectedError: eris.New("oauth-settings-incorrect"),
},
{
name: "Missing ClientSecret",
mockSettings: setting.OAuthSettings{
ClientID: "valid-client-id",
AuthURL: "https://auth.url",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
},
expectedError: eris.New("oauth-settings-incorrect"),
},
{
name: "Missing AuthURL",
mockSettings: setting.OAuthSettings{
ClientID: "valid-client-id",
ClientSecret: "valid-client-secret",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
},
expectedError: eris.New("oauth-settings-incorrect"),
},
{
name: "Missing TokenURL",
mockSettings: setting.OAuthSettings{
ClientID: "valid-client-id",
ClientSecret: "valid-client-secret",
AuthURL: "https://auth.url",
Scopes: []string{"scope1", "scope2"},
},
expectedError: eris.New("oauth-settings-incorrect"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock the GetOAuthSettings function
settingGetOAuthSettings = func() (setting.OAuthSettings, error) {
return tt.mockSettings, nil
}
config, settings, err := getOAuth2Config()
if tt.expectedError != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
} else {
assert.NoError(t, err)
assert.NotNil(t, config)
assert.NotNil(t, settings)
assert.Equal(t, tt.mockSettings.ClientID, config.ClientID)
assert.Equal(t, tt.mockSettings.ClientSecret, config.ClientSecret)
assert.Equal(t, tt.mockSettings.AuthURL, config.Endpoint.AuthURL)
assert.Equal(t, tt.mockSettings.TokenURL, config.Endpoint.TokenURL)
assert.Equal(t, tt.mockSettings.Scopes, config.Scopes)
}
})
}
}
func TestGetEmail(t *testing.T) {
tests := []struct {
name string
oauthUser OAuthUser
expected string
}{
{
name: "Email in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"email": "user@example.com",
},
},
expected: "user@example.com",
},
{
name: "Identifier is email",
oauthUser: OAuthUser{
Identifier: "user@example.com",
},
expected: "user@example.com",
},
{
name: "Identifier is not email",
oauthUser: OAuthUser{
Identifier: "user123",
},
expected: "user123@oauth",
},
{
name: "No email or identifier",
oauthUser: OAuthUser{
Resource: map[string]any{},
},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
email := tt.oauthUser.GetEmail()
assert.Equal(t, tt.expected, email)
})
}
}
func TestGetName(t *testing.T) {
tests := []struct {
name string
oauthUser OAuthUser
expected string
}{
{
name: "Nickname in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"nickname": "user_nick",
},
},
expected: "user_nick",
},
{
name: "Given name in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"given_name": "User Given",
},
},
expected: "User Given",
},
{
name: "Name in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"name": "User Name",
},
},
expected: "User Name",
},
{
name: "Preferred username in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"preferred_username": "preferred_user",
},
},
expected: "preferred_user",
},
{
name: "Username in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"username": "user123",
},
},
expected: "user123",
},
{
name: "No name fields in resource, fallback to identifier",
oauthUser: OAuthUser{
Identifier: "fallback_identifier",
Resource: map[string]any{},
},
expected: "fallback_identifier",
},
{
name: "No name fields and no identifier",
oauthUser: OAuthUser{
Resource: map[string]any{},
},
expected: "",
},
{
name: "All fields",
oauthUser: OAuthUser{
Identifier: "fallback_identifier",
Resource: map[string]any{
"nickname": "user_nick",
"given_name": "User Given",
"name": "User Name",
"preferred_username": "preferred_user",
"username": "user123",
},
},
expected: "user_nick",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name := tt.oauthUser.GetName()
assert.Equal(t, tt.expected, name)
})
}
}
func TestGetID(t *testing.T) {
tests := []struct {
name string
oauthUser OAuthUser
expected string
}{
{
name: "Identifier is set",
oauthUser: OAuthUser{
Identifier: "user123",
},
expected: "user123",
},
{
name: "UID in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"uid": "uid123",
},
},
expected: "uid123",
},
{
name: "User ID in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"user_id": "user_id123",
},
},
expected: "user_id123",
},
{
name: "Username in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"username": "username123",
},
},
expected: "username123",
},
{
name: "Preferred username in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"preferred_username": "preferred_user",
},
},
expected: "preferred_user",
},
{
name: "Email in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"email": "user@example.com",
},
},
expected: "user@example.com",
},
{
name: "Mail in resource",
oauthUser: OAuthUser{
Resource: map[string]any{
"mail": "mail@example.com",
},
},
expected: "mail@example.com",
},
{
name: "No identifier or resource fields",
oauthUser: OAuthUser{
Resource: map[string]any{},
},
expected: "",
},
{
name: "All fields",
oauthUser: OAuthUser{
Identifier: "user123",
Resource: map[string]any{
"uid": "uid123",
"user_id": "user_id123",
"username": "username123",
"preferred_username": "preferred_user",
"mail": "mail@example.com",
"email": "email@example.com",
},
},
expected: "user123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
id := tt.oauthUser.GetID()
assert.Equal(t, tt.expected, id)
})
}
}
func TestOAuthLogin(t *testing.T) {
tests := []struct {
name string
redirectBase string
ipAddress string
expectedError error
}{
{
name: "Valid redirect base",
redirectBase: "https://redirect.base",
ipAddress: "127.0.0.1",
expectedError: nil,
},
{
name: "Empty redirect base",
redirectBase: "",
ipAddress: "127.0.0.1",
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock the GetOAuthSettings function
settingGetOAuthSettings = func() (setting.OAuthSettings, error) {
return setting.OAuthSettings{
ClientID: "valid-client-id",
ClientSecret: "valid-client-secret",
AuthURL: "https://auth.url",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
}, nil
}
url, err := OAuthLogin(tt.redirectBase, tt.ipAddress)
if tt.expectedError != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
} else {
assert.NoError(t, err)
assert.NotEmpty(t, url)
}
})
}
}
func TestOAuthReturn(t *testing.T) {
var errNotFound = eris.New("oauth-verifier-not-found")
tests := []struct {
name string
code string
ipAddress string
expectedError error
}{
{
name: "Invalid code",
code: "invalid-code",
ipAddress: "127.0.0.100",
expectedError: errNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock the GetOAuthSettings function
settingGetOAuthSettings = func() (setting.OAuthSettings, error) {
return setting.OAuthSettings{
ClientID: "valid-client-id",
ClientSecret: "valid-client-secret",
AuthURL: "https://auth.url",
TokenURL: "https://token.url",
Scopes: []string{"scope1", "scope2"},
ResourceURL: "https://resource.url",
Identifier: "id",
}, nil
}
// Initialise the cache and set a verifier
OAuthCacheInit()
if tt.expectedError != errNotFound {
OAuthCache.Set(getCacheKey(tt.ipAddress), "valid-verifier", cache.DefaultExpiration)
}
ctx := context.Background()
user, err := OAuthReturn(ctx, tt.code, tt.ipAddress)
if tt.expectedError != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
} else {
assert.NoError(t, err)
assert.NotNil(t, user)
}
})
}
}

View File

@ -44,7 +44,7 @@ const (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Type string `json:"type" gorm:"column:type" filter:"type,string"`
CertificateAuthorityID types.NullableDBUint `json:"certificate_authority_id" gorm:"column:certificate_authority_id" filter:"certificate_authority_id,integer"`

View File

@ -210,7 +210,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
}

View File

@ -13,7 +13,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
Name string `json:"name" gorm:"column:name" filter:"name,string"`
AcmeshServer string `json:"acmesh_server" gorm:"column:acmesh_server" filter:"acmesh_server,string"`
CABundle string `json:"ca_bundle" gorm:"column:ca_bundle" filter:"ca_bundle,string"`

View File

@ -296,7 +296,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
}

View File

@ -14,7 +14,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Name string `json:"name" gorm:"column:name" filter:"name,string"`
AcmeshName string `json:"acmesh_name" gorm:"column:acmesh_name" filter:"acmesh_name,string"`
@ -70,8 +70,8 @@ func (m *Model) GetAcmeShEnvVars() ([]string, error) {
return envs, nil
}
func getEnvsFromMeta(meta interface{}) []string {
if rec, ok := meta.(map[string]interface{}); ok {
func getEnvsFromMeta(meta any) []string {
if rec, ok := meta.(map[string]any); ok {
envs := make([]string, 0)
for key, val := range rec {
if f, ok := val.(string); ok {
@ -81,8 +81,8 @@ func getEnvsFromMeta(meta interface{}) []string {
}
}
return envs
} else {
logger.Debug("getEnvsFromMeta: meta is not an map of strings")
return nil
}
logger.Debug("getEnvsFromMeta: meta is not an map of strings")
return nil
}

View File

@ -6,24 +6,14 @@ import (
)
// GetFilterMap returns the filter map
func GetFilterMap(m interface{}, includeBaseEntity bool) map[string]model.FilterMapValue {
// _ was called `includeBaseEntity`
func GetFilterMap(m any, _ bool) map[string]model.FilterMapValue {
filterMap := tags.GetFilterMap(m, "")
// TODO: this is done in GetFilterMap isn't it?
// if includeBaseEntity {
// return mergeFilterMaps(tags.GetFilterMap(model.ModelBase{}, ""), filterMap)
// return mergeFilterMaps(tags.GetFilterMap(model.Base{}, ""), filterMap)
// }
return filterMap
}
// func mergeFilterMaps(m1 map[string]model.FilterMapValue, m2 map[string]model.FilterMapValue) map[string]model.FilterMapValue {
// merged := make(map[string]model.FilterMapValue, 0)
// for k, v := range m1 {
// merged[k] = v
// }
// for key, value := range m2 {
// merged[key] = value
// }
// return merged
// }

View File

@ -189,7 +189,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
}
@ -203,7 +203,7 @@ func (s *testsuite) TestGetTemplate() {
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
m := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
CreatedAt: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
UpdatedAt: time.Date(2018, 8, 12, 7, 30, 24, 16, time.UTC).UnixMilli(),

View File

@ -27,7 +27,7 @@ const (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Type string `json:"type" gorm:"column:type" filter:"type,string"`
NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"`

View File

@ -14,12 +14,12 @@ type ListResponse struct {
Limit int `json:"limit"`
Sort []model.Sort `json:"sort"`
Filter []model.Filter `json:"filter,omitempty"`
Items interface{} `json:"items,omitempty"`
Items any `json:"items,omitempty"`
}
// ListQueryBuilder is used to setup queries for lists
func ListQueryBuilder(
pageInfo *model.PageInfo,
_ *model.PageInfo,
filters []model.Filter,
filterMap map[string]model.FilterMapValue,
) *gorm.DB {

View File

@ -9,7 +9,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Name string `json:"name" gorm:"column:name" filter:"name,string"`
Type string `json:"type" gorm:"column:type" filter:"type,string"`

View File

@ -10,6 +10,7 @@ import (
"gorm.io/gorm"
)
// ScopeOffsetLimit ...
func ScopeOffsetLimit(pageInfo *model.PageInfo) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if pageInfo.Offset > 0 || pageInfo.Limit > 0 {
@ -19,6 +20,7 @@ func ScopeOffsetLimit(pageInfo *model.PageInfo) func(db *gorm.DB) *gorm.DB {
}
}
// ScopeOrderBy ...
func ScopeOrderBy(sort []model.Sort, defaultSort model.Sort) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if sort != nil {
@ -36,6 +38,7 @@ func ScopeOrderBy(sort []model.Sort, defaultSort model.Sort) func(db *gorm.DB) *
}
}
// ScopeFilters ...
func ScopeFilters(filters []model.Filter, filterMap map[string]model.FilterMapValue) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
like := database.GetCaseInsensitiveLike()

View File

@ -0,0 +1,33 @@
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseBoolValue(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"yes", []string{"1"}},
{"true", []string{"1"}},
{"on", []string{"1"}},
{"t", []string{"1"}},
{"1", []string{"1"}},
{"y", []string{"1"}},
{"no", []string{"0"}},
{"false", []string{"0"}},
{"off", []string{"0"}},
{"f", []string{"0"}},
{"0", []string{"0"}},
{"n", []string{"0"}},
{"random", []string{"0"}},
}
for _, test := range tests {
result := parseBoolValue(test.input)
assert.Equal(t, test.expected, result, "Input: %s", test.input)
}
}

View File

@ -2,19 +2,31 @@ package setting
import (
"encoding/json"
"slices"
)
// GetAuthMethods returns the authentication methods enabled for this site
func GetAuthMethods() ([]string, error) {
var l []string
var m Model
if err := m.LoadByName("auth-methods"); err != nil {
return l, err
return nil, err
}
if err := json.Unmarshal([]byte(m.Value.String()), &l); err != nil {
return l, err
var r []string
if err := json.Unmarshal([]byte(m.Value.String()), &r); err != nil {
return nil, err
}
return l, nil
return r, nil
}
// AuthMethodEnabled checks that the auth method given is
// enabled in the db setting
func AuthMethodEnabled(method string) bool {
r, err := GetAuthMethods()
if err != nil {
return false
}
return slices.Contains(r, method)
}

View File

@ -11,7 +11,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
Name string `json:"name" gorm:"column:name" filter:"name,string"`
Description string `json:"description" gorm:"column:description" filter:"description,string"`
Value datatypes.JSON `json:"value" gorm:"column:value"`

View File

@ -0,0 +1,42 @@
package setting
import (
"encoding/json"
)
// OAuthSettings are the settings for OAuth that come from
// the `oauth-auth` setting value
type OAuthSettings struct {
AutoCreateUser bool `json:"auto_create_user"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
AuthURL string `json:"authorization_url"`
TokenURL string `json:"token_url"`
Identifier string `json:"identifier"`
LogoutURL string `json:"logout_url"`
Scopes []string `json:"scopes"`
ResourceURL string `json:"resource_url"`
}
// GetOAuthSettings will return the OAuth settings
func GetOAuthSettings() (OAuthSettings, error) {
var o OAuthSettings
var m Model
if err := m.LoadByName("oauth-auth"); err != nil {
return o, err
}
if err := json.Unmarshal([]byte(m.Value.String()), &o); err != nil {
return o, err
}
o.ApplyDefaults()
return o, nil
}
// ApplyDefaults will ensure there are defaults set
func (m *OAuthSettings) ApplyDefaults() {
if m.Identifier == "" {
m.Identifier = "email"
}
}

View File

@ -10,7 +10,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
ExpiresOn types.DBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"`
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Provider string `json:"provider" gorm:"column:provider" filter:"provider,string"`

View File

@ -17,7 +17,7 @@ import (
// Model is the model
// See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
type Model struct {
model.ModelBase
model.Base
UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Name string `json:"name" gorm:"column:name" filter:"name,string"`
NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"`

View File

@ -7,7 +7,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"`
Server string `json:"server" gorm:"column:server" filter:"server,string"`
Weight int `json:"weight" gorm:"column:weight" filter:"weight,integer"`

View File

@ -226,7 +226,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
Name: "John Doe",
@ -254,6 +254,10 @@ func (s *testsuite) TestDeleteAll() {
WithArgs(false).
WillReturnResult(sqlmock.NewResult(0, 1))
s.mock.
ExpectExec(regexp.QuoteMeta(`DELETE FROM "auth"`)).
WillReturnResult(sqlmock.NewResult(0, 1))
err := DeleteAll()
require.NoError(s.T(), err)
require.NoError(s.T(), s.mock.ExpectationsWereMet())
@ -438,7 +442,7 @@ func (s *testsuite) TestSaveCapabilitiesInvalid() {
// Empty model returns error
m := Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
Capabilities: []string{"doesnotexist", "hosts.manage"},

View File

@ -89,14 +89,26 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
// DeleteAll will do just that, and should only be used for testing purposes.
func DeleteAll() error {
db := database.GetDB()
result := db.Exec(fmt.Sprintf(`DELETE FROM %s WHERE is_system = ?`, database.QuoteTableName("user")), false)
return result.Error
if result := db.Exec(
fmt.Sprintf(`DELETE FROM %s WHERE is_system = ?`, database.QuoteTableName("user")),
false,
); result.Error != nil {
return result.Error
}
if result := db.Exec(
fmt.Sprintf(`DELETE FROM %s`, database.QuoteTableName("auth")),
); result.Error != nil {
return result.Error
}
return nil
}
// GetCapabilities gets capabilities for a user
func GetCapabilities(userID uint) ([]string, error) {
capabilities := make([]string, 0)
var hasCapabilities []UserHasCapabilityModel
var hasCapabilities []HasCapabilityModel
db := database.GetDB()
if result := db.Where("user_id = ?", userID).Find(&hasCapabilities); result.Error != nil {
return nil, result.Error
@ -117,3 +129,14 @@ func CreateFromLDAPUser(ldapUser *auth.LDAPUser) (Model, error) {
user.generateGravatar()
return user, err
}
// CreateFromOAuthUser will create a user from an OAuth user object
func CreateFromOAuthUser(ou *auth.OAuthUser) (Model, error) {
user := Model{
Email: ou.GetEmail(),
Name: ou.GetName(),
}
err := user.Save()
user.generateGravatar()
return user, err
}

View File

@ -16,7 +16,7 @@ import (
// Model is the model
type Model struct {
model.ModelBase
model.Base
Name string `json:"name" gorm:"column:name" filter:"name,string"`
Email string `json:"email" gorm:"column:email" filter:"email,email"`
IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"`
@ -33,14 +33,14 @@ func (Model) TableName() string {
return "user"
}
// UserHasCapabilityModel is the model
type UserHasCapabilityModel struct {
// HasCapabilityModel is the model
type HasCapabilityModel struct {
UserID uint `json:"user_id" gorm:"column:user_id"`
CapabilityName string `json:"name" gorm:"column:capability_name"`
}
// TableName overrides the table name used by gorm
func (UserHasCapabilityModel) TableName() string {
func (HasCapabilityModel) TableName() string {
return "user_has_capability"
}
@ -99,15 +99,15 @@ func (m *Model) SetPermissions(permissions []string) error {
db := database.GetDB()
// Wipe out previous permissions
if result := db.Where("user_id = ?", m.ID).Delete(&UserHasCapabilityModel{}); result.Error != nil {
if result := db.Where("user_id = ?", m.ID).Delete(&HasCapabilityModel{}); result.Error != nil {
return result.Error
}
if len(permissions) > 0 {
// Add new permissions
objs := []*UserHasCapabilityModel{}
objs := []*HasCapabilityModel{}
for _, permission := range permissions {
objs = append(objs, &UserHasCapabilityModel{UserID: m.ID, CapabilityName: permission})
objs = append(objs, &HasCapabilityModel{UserID: m.ID, CapabilityName: permission})
}
if result := db.Create(objs); result.Error != nil {
return result.Error

View File

@ -16,7 +16,7 @@ var (
func Start() {
ctx, cancel = context.WithCancel(context.Background())
q := &Queue{
jobs: make(chan Job),
jobs: make(chan Job, 50),
ctx: ctx,
cancel: cancel,
}
@ -34,6 +34,8 @@ func Shutdown() error {
return eris.New("Unable to shutdown, jobqueue has not been started")
}
cancel()
worker = nil
cancel = nil
return nil
}

View File

@ -0,0 +1,67 @@
package jobqueue
import (
"context"
"fmt"
"testing"
"time"
"github.com/rotisserie/eris"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type MockJob struct {
done chan bool
}
func (m *MockJob) Execute() {
time.Sleep(1 * time.Second)
m.done <- true
}
func TestStart(t *testing.T) {
Start()
assert.NotNil(t, ctx, "Context should not be nil after Start")
assert.NotNil(t, cancel, "Cancel function should not be nil after Start")
assert.NotNil(t, worker, "Worker should not be nil after Start")
Shutdown()
}
func TestShutdown(t *testing.T) {
Start()
err := Shutdown()
require.Nil(t, err, "Shutdown should not return an error when jobqueue is started")
// nolint: gosimple
select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
default:
require.Nil(t, ctx.Err(), "Context done state has unexpected value")
}
}
require.Nil(t, cancel, "Cancel function should be nil after Shutdown")
require.Nil(t, worker, "Worker should be nil after Shutdown")
err = Shutdown()
require.NotNil(t, err, "Shutdown should return an error when jobqueue is not started")
require.Equal(t, eris.New("Unable to shutdown, jobqueue has not been started").Error(), err.Error())
}
func TestAddJobWithoutStart(t *testing.T) {
mockJob := Job{
Name: "mockJob",
Action: func() error {
return nil
},
}
err := AddJob(mockJob)
assert.NotNil(t, err, "AddJob should return an error when jobqueue is not started")
assert.Equal(t, eris.New("Unable to add job, jobqueue has not been started").Error(), err.Error())
}

View File

@ -10,6 +10,7 @@ type Queue struct {
jobs chan Job
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
}
// Job - holds logic to perform some operations during queue execution.
@ -40,15 +41,12 @@ func (q *Queue) AddJobs(jobs []Job) {
// AddJob sends job to the channel.
func (q *Queue) AddJob(job Job) {
q.mu.Lock()
defer q.mu.Unlock()
q.jobs <- job
}
// Run performs job execution.
func (j Job) Run() error {
err := j.Action()
if err != nil {
return err
}
return nil
func (j *Job) Run() error {
return j.Action()
}

View File

@ -2,6 +2,7 @@ package jobqueue
import (
"fmt"
"npm/internal/logger"
)

View File

@ -9,7 +9,7 @@ var currentKeys KeysModel
// KeysModel is the model
type KeysModel struct {
model.ModelBase
model.Base
PublicKey string `gorm:"column:public_key"`
PrivateKey string `gorm:"column:private_key"`
}
@ -19,7 +19,7 @@ func (KeysModel) TableName() string {
return "jwt_keys"
}
// LoadByID will load from an ID
// LoadLatest will load the latest keys
func (m *KeysModel) LoadLatest() error {
db := database.GetDB()
result := db.Order("created_at DESC").First(&m)

View File

@ -183,7 +183,7 @@ func (s *testsuite) TestGetPrivateKey() {
// Set currentKeys and try again
currentKeys = KeysModel{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
PrivateKey: s.privateKeyString,
@ -210,7 +210,7 @@ func (s *testsuite) TestGetPublicKey() {
// Set currentKeys and try again
currentKeys = KeysModel{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
PrivateKey: s.privateKeyString,
@ -228,7 +228,7 @@ func (s *testsuite) TestGenerate() {
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
currentKeys = KeysModel{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
PrivateKey: s.privateKeyString,
@ -236,7 +236,7 @@ func (s *testsuite) TestGenerate() {
}
usr := user.Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
}

View File

@ -24,11 +24,11 @@ type Config struct {
// Interface for a logger
type Interface interface {
GetLogLevel() Level
Debug(format string, args ...interface{})
Info(format string, args ...interface{})
Warn(format string, args ...interface{})
Error(errorClass string, err error, args ...interface{})
Errorf(errorClass, format string, err error, args ...interface{})
Debug(format string, args ...any)
Info(format string, args ...any)
Warn(format string, args ...any)
Error(errorClass string, err error, args ...any)
Errorf(errorClass, format string, err error, args ...any)
}
// ConfigurableLogger is an interface for a logger that can be configured

View File

@ -71,17 +71,17 @@ func GetLogLevel() Level {
}
// Debug logs if the log level is set to DebugLevel or below. Arguments are handled in the manner of fmt.Printf.
func Debug(format string, args ...interface{}) {
func Debug(format string, args ...any) {
logger.Debug(format, args...)
}
// Info logs if the log level is set to InfoLevel or below. Arguments are handled in the manner of fmt.Printf.
func Info(format string, args ...interface{}) {
func Info(format string, args ...any) {
logger.Info(format, args...)
}
// Warn logs if the log level is set to WarnLevel or below. Arguments are handled in the manner of fmt.Printf.
func Warn(format string, args ...interface{}) {
func Warn(format string, args ...any) {
logger.Warn(format, args...)
}
@ -134,7 +134,7 @@ var logLevels = map[Level]string{
ErrorLevel: "ERROR",
}
func (l *Logger) logLevel(logLevel Level, format string, args ...interface{}) {
func (l *Logger) logLevel(logLevel Level, format string, args ...any) {
if logLevel < l.LogThreshold {
return
}
@ -146,7 +146,7 @@ func (l *Logger) logLevel(logLevel Level, format string, args ...interface{}) {
if len(args) > 1 {
args = args[1:]
} else {
args = []interface{}{}
args = []any{}
}
}
@ -202,17 +202,17 @@ func (l *Logger) GetLogLevel() Level {
}
// Debug logs if the log level is set to DebugLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Debug(format string, args ...interface{}) {
func (l *Logger) Debug(format string, args ...any) {
l.logLevel(DebugLevel, format, args...)
}
// Info logs if the log level is set to InfoLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Info(format string, args ...interface{}) {
func (l *Logger) Info(format string, args ...any) {
l.logLevel(InfoLevel, format, args...)
}
// Warn logs if the log level is set to WarnLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Warn(format string, args ...interface{}) {
func (l *Logger) Warn(format string, args ...any) {
l.logLevel(WarnLevel, format, args...)
}

View File

@ -4,8 +4,8 @@ import (
"gorm.io/plugin/soft_delete"
)
// ModelBase include common fields for db control
type ModelBase struct {
// Base include common fields for db control
type Base struct {
ID uint `json:"id" gorm:"column:id;primaryKey" filter:"id,integer"`
CreatedAt int64 `json:"created_at" gorm:"<-:create;autoCreateTime:milli;column:created_at" filter:"created_at,date"`
UpdatedAt int64 `json:"updated_at" gorm:"<-;autoUpdateTime:milli;column:updated_at" filter:"updated_at,date"`

View File

@ -151,14 +151,14 @@ func ConfigureUpstream(u upstream.Model) error {
return u.Save(true)
}
func getHostFilename(h host.Model, append string) string {
func getHostFilename(h host.Model, appends string) string {
confDir := fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder)
return fmt.Sprintf("%s/host_%d.conf%s", confDir, h.ID, append)
return fmt.Sprintf("%s/host_%d.conf%s", confDir, h.ID, appends)
}
func getUpstreamFilename(u upstream.Model, append string) string {
func getUpstreamFilename(u upstream.Model, appends string) string {
confDir := fmt.Sprintf("%s/nginx/upstreams", config.Configuration.DataFolder)
return fmt.Sprintf("%s/upstream_%d.conf%s", confDir, u.ID, append)
return fmt.Sprintf("%s/upstream_%d.conf%s", confDir, u.ID, appends)
}
func removeHostFiles(h host.Model) {

View File

@ -1,10 +1,11 @@
package nginx
import (
"testing"
"npm/internal/entity/host"
"npm/internal/model"
"npm/internal/test"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
@ -24,7 +25,7 @@ func TestGetHostFilename(t *testing.T) {
{
"test1",
host.Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
},
@ -34,7 +35,7 @@ func TestGetHostFilename(t *testing.T) {
{
"test2",
host.Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 10,
},
},

View File

@ -54,7 +54,7 @@ server {
IsDisabled: false,
},
cert: certificate.Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 77,
},
Status: certificate.StatusProvided,
@ -79,7 +79,7 @@ server {
IsDisabled: false,
},
cert: certificate.Model{
ModelBase: model.ModelBase{
Base: model.Base{
ID: 66,
},
Status: certificate.StatusProvided,
@ -108,18 +108,18 @@ server {
},
}
for _, test := range tests {
t.Run(test.name, func(st *testing.T) {
for _, tst := range tests {
t.Run(tst.name, func(st *testing.T) {
templateData := TemplateData{
ConfDir: "/etc/nginx/conf.d",
DataDir: "/data",
Host: test.host.GetTemplate(),
Certificate: test.cert.GetTemplate(),
Host: tst.host.GetTemplate(),
Certificate: tst.cert.GetTemplate(),
}
output, err := renderTemplate(template, templateData)
assert.Equal(t, test.want.err, err)
assert.Equal(t, test.want.output, output)
assert.Equal(st, tst.want.err, err)
assert.Equal(st, tst.want.output, output)
})
}
}

View File

@ -26,7 +26,7 @@ func Get() *sse.Server {
if instance == nil {
instance = sse.NewServer(&sse.Options{
Logger: logger.Get(),
ChannelNameFunc: func(request *http.Request) string {
ChannelNameFunc: func(_ *http.Request) string {
return defaultChannel // This is the channel for all updates regardless of visibility
},
})

View File

@ -14,7 +14,8 @@ import (
"github.com/rotisserie/eris"
)
func GetFilterMap(m interface{}, globalTablePrefix string) map[string]model.FilterMapValue {
// GetFilterMap ...
func GetFilterMap(m any, globalTablePrefix string) map[string]model.FilterMapValue {
name := getName(m)
filterMap := make(map[string]model.FilterMapValue)
@ -39,8 +40,8 @@ func GetFilterMap(m interface{}, globalTablePrefix string) map[string]model.Filt
// If this is an entity model (and it probably is)
// then include the base model as well
if strings.Contains(name, ".Model") && !strings.Contains(name, "ModelBase") {
filterMap = GetFilterMap(model.ModelBase{}, globalTablePrefix)
if strings.Contains(name, ".Model") && !strings.Contains(name, "Base") {
filterMap = GetFilterMap(model.Base{}, globalTablePrefix)
}
if t.Kind() != reflect.Struct {
@ -128,7 +129,7 @@ func getFilterTagSchema(filterTag string) string {
// GetFilterSchema creates a jsonschema for validating filters, based on the model
// object given and by reading the struct "filter" tags.
func GetFilterSchema(m interface{}) string {
func GetFilterSchema(m any) string {
filterMap := GetFilterMap(m, "")
schemas := make([]string, 0)

View File

@ -10,7 +10,7 @@ import (
var tagCache map[string]map[string]model.FilterMapValue
// getName returns the name of the type given
func getName(m interface{}) string {
func getName(m any) string {
fc := reflect.TypeOf(m)
return fmt.Sprint(fc)
}

View File

@ -12,6 +12,7 @@ import (
"gorm.io/gorm"
)
// Setup ...
func Setup() (sqlmock.Sqlmock, error) {
db, mock, err := sqlmock.New()
if err != nil {
@ -26,6 +27,7 @@ func Setup() (sqlmock.Sqlmock, error) {
return mock, err
}
// InitConfig ...
func InitConfig(t *testing.T, envs ...string) {
if len(envs) > 0 {
for _, env := range envs {

View File

@ -18,7 +18,7 @@ func (d DBDate) Value() (driver.Value, error) {
}
// Scan takes data from the database and modifies it for Go Types
func (d *DBDate) Scan(src interface{}) error {
func (d *DBDate) Scan(src any) error {
d.Time = time.Unix(src.(int64), 0)
return nil
}

View File

@ -22,7 +22,7 @@ func (d NullableDBInt) Value() (driver.Value, error) {
}
// Scan takes data from the database and modifies it for Go Types
func (d *NullableDBInt) Scan(src interface{}) error {
func (d *NullableDBInt) Scan(src any) error {
var i int
switch v := src.(type) {
case int:

View File

@ -18,16 +18,19 @@ func (d NullableDBUint) Value() (driver.Value, error) {
}
// According to current database/sql docs, the sql has four builtin functions that
// returns driver.Value, and the underlying types are `int64`, `float64`, `string` and `bool`
// nolint: gosec
return driver.Value(int64(d.Uint)), nil
}
// Scan takes data from the database and modifies it for Go Types
func (d *NullableDBUint) Scan(src interface{}) error {
func (d *NullableDBUint) Scan(src any) error {
var i uint
switch v := src.(type) {
case int:
// nolint: gosec
i = uint(v)
case int64:
// nolint: gosec
i = uint(v)
case float32:
i = uint(v)
@ -35,6 +38,7 @@ func (d *NullableDBUint) Scan(src interface{}) error {
i = uint(v)
case string:
a, _ := strconv.Atoi(v)
// nolint: gosec
i = uint(a)
}
d.Uint = i

View File

@ -50,7 +50,7 @@ func TestNullableDBUint_Scan(t *testing.T) {
tests := []struct {
name string
input interface{}
input any
wantUint uint
wantErr bool
}{

View File

@ -9,18 +9,18 @@ import (
// JSONB can be anything
type JSONB struct {
Encoded string `json:"decoded"`
Decoded interface{} `json:"encoded"`
Encoded string `json:"decoded"`
Decoded any `json:"encoded"`
}
// Value encodes the type ready for the database
func (j JSONB) Value() (driver.Value, error) {
json, err := json.Marshal(j.Decoded)
return driver.Value(string(json)), err
jsn, err := json.Marshal(j.Decoded)
return driver.Value(string(jsn)), err
}
// Scan takes data from the database and modifies it for Go Types
func (j *JSONB) Scan(src interface{}) error {
func (j *JSONB) Scan(src any) error {
var jsonb JSONB
var srcString string
switch v := src.(type) {

View File

@ -8,7 +8,7 @@ import (
// TestJSONBValue tests the Value method of the JSONB type
func TestJSONBValue(t *testing.T) {
j := JSONB{
Decoded: map[string]interface{}{
Decoded: map[string]any{
"name": "John",
"age": 30,
},
@ -35,7 +35,7 @@ func TestJSONBScan(t *testing.T) {
t.Errorf("Unexpected error: %v", err)
}
expectedDecoded := map[string]interface{}{
expectedDecoded := map[string]any{
"name": "John",
"age": 30,
}
@ -59,7 +59,7 @@ func TestJSONBUnmarshalJSON(t *testing.T) {
t.Errorf("Unexpected error: %v", err)
}
expectedDecoded := map[string]interface{}{
expectedDecoded := map[string]any{
"name": "John",
"age": 30,
}
@ -76,7 +76,7 @@ func TestJSONBUnmarshalJSON(t *testing.T) {
// TestJSONBMarshalJSON tests the MarshalJSON method of the JSONB type
func TestJSONBMarshalJSON(t *testing.T) {
j := JSONB{
Decoded: map[string]interface{}{
Decoded: map[string]any{
"name": "John",
"age": 30,
},
@ -113,7 +113,7 @@ func TestJSONBAsStringArray(t *testing.T) {
}
// Helper function to compare JSON objects
func jsonEqual(a, b interface{}) bool {
func jsonEqual(a, b any) bool {
aJSON, _ := json.Marshal(a)
bJSON, _ := json.Marshal(b)
return string(aJSON) == string(bJSON)

View File

@ -23,7 +23,7 @@ func (d NullableDBDate) Value() (driver.Value, error) {
}
// Scan takes data from the database and modifies it for Go Types
func (d *NullableDBDate) Scan(src interface{}) error {
func (d *NullableDBDate) Scan(src any) error {
var tme time.Time
if src != nil {
tme = time.Unix(src.(int64), 0)

View File

@ -1,9 +1,9 @@
package util
// FindItemInInterface Find key in interface (recursively) and return value as interface
func FindItemInInterface(key string, obj interface{}) (interface{}, bool) {
func FindItemInInterface(key string, obj any) (any, bool) {
// if the argument is not a map, ignore it
mobj, ok := obj.(map[string]interface{})
mobj, ok := obj.(map[string]any)
if !ok {
return nil, false
}
@ -15,14 +15,14 @@ func FindItemInInterface(key string, obj interface{}) (interface{}, bool) {
}
// if the value is a map, search recursively
if m, ok := v.(map[string]interface{}); ok {
if m, ok := v.(map[string]any); ok {
if res, ok := FindItemInInterface(key, m); ok {
return res, true
}
}
// if the value is an array, search recursively
// from each element
if va, ok := v.([]interface{}); ok {
if va, ok := v.([]any); ok {
for _, a := range va {
if res, ok := FindItemInInterface(key, a); ok {
return res, true

View File

@ -11,13 +11,13 @@ func TestFindItemInInterface(t *testing.T) {
// goleak is used to detect goroutine leaks
defer goleak.VerifyNone(t, goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
obj := map[string]interface{}{
obj := map[string]any{
"key1": "value1",
"key2": 10,
"key3": map[string]interface{}{
"key3": map[string]any{
"nestedKey": "nestedValue",
},
"key4": []interface{}{"item1", "item2"},
"key4": []any{"item1", "item2"},
}
// Test case 1: Key exists at the top level

Some files were not shown because too many files have changed in this diff Show More