Compare commits

..

72 commits

Author SHA1 Message Date
c113ac8a44 chore(deps): update dependency @sveltejs/vite-plugin-svelte to v5
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
2025-05-22 16:30:07 +00:00
9466d1b17a Add tests on authuser and authentication usecases
Some checks failed
continuous-integration/drone/push Build is failing
2025-05-22 15:46:06 +02:00
a70bd8bc79 Refactor usecases: create intermediate structs to facilitate tests 2025-05-22 15:46:06 +02:00
84f10782b0 Refactor domain/provider/zone usecase: split in multiple files 2025-05-22 15:46:06 +02:00
263a9d60ef Refactor domain's logs usecase: split in multiple files 2025-05-22 15:46:06 +02:00
a4317c8498 Refactor user usecase: split in multiple files 2025-05-22 15:46:06 +02:00
ceeb045154 Refactor auth_user usecase: split in multiple files 2025-05-22 15:46:06 +02:00
030bdbe9e3 Refactor session usecase: split in multiple files 2025-05-22 15:46:06 +02:00
ee2f033b0d Move config struct to model, avoid dependancy to storage 2025-05-22 15:46:06 +02:00
34f1404adb Improve error report 2025-05-22 15:46:06 +02:00
3113c9f01a refactor: Move storage interfaces to usecases 2025-05-22 15:46:06 +02:00
1288e7da35 refactor: restructure project architecture and folder hierarchy
- Moved usecases into internal/usecase
- Renamed internal/utils to internal/helpers for better naming semantics
- Clarified separation between domain model, usecases, adapters, and web/API
2025-05-22 15:46:06 +02:00
b77e2b8c3a Refactor App to add clarity 2025-05-22 15:46:06 +02:00
fb971236f9 Rename Service to Usecase to be consistent 2025-05-22 15:46:06 +02:00
2f21ebee44 Rename ProviderId attribute 2025-05-22 15:46:06 +02:00
2e9b9e6adf Standardize databases errors 2025-05-22 15:46:06 +02:00
5d0fe5edb5 Remove plural structs not used as struct 2025-05-22 15:46:06 +02:00
eb60145ca2 Rename DoMigration to improve self-readability 2025-05-22 15:46:05 +02:00
3762b9695c Add an in-memory database, useful for testing 2025-05-22 15:46:05 +02:00
d427ef4377 Move tidy operation to a usecase 2025-05-22 15:46:05 +02:00
401ba892f7 Use iterators in return to ListAll database functions 2025-05-22 15:46:05 +02:00
dce7e59983 Create a generic error for database not found field 2025-05-22 15:46:05 +02:00
001e352e57 Refactor database interface in smaller interfaces 2025-05-22 15:46:05 +02:00
a224f0e212 Simplify database calls and move the logic in usecases: authUsers 2025-05-22 15:46:05 +02:00
72b9602f71 Update database calls terminology 2025-05-22 15:46:05 +02:00
f4daca892e Simplify database calls and move the logic in usecases: providers 2025-05-22 15:46:05 +02:00
9152a2697b Simplify database calls and move the logic in usecases: domains 2025-05-22 15:46:05 +02:00
6c2aaa2f6e Standardize owner field name for domains and providers 2025-05-22 15:46:05 +02:00
c58c1f1cc0 fix(deps): update module github.com/gin-gonic/gin to v1.10.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-20 10:29:25 +00:00
f1e698d867 chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-19 00:33:09 +00:00
48a801f725 fix(deps): update module github.com/yuin/goldmark to v1.7.12
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-17 13:25:48 +00:00
96b12f797e chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-12 00:32:18 +00:00
996b20e67b Fix a panic with SRV on classic subdomain 2025-05-10 00:11:14 +02:00
821a8d5b68 fix(deps): update module github.com/miekg/dns to v1.1.66
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-07 19:25:24 +00:00
96a461db0a fix(deps): update module golang.org/x/crypto to v0.38.0
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-05 19:24:10 +00:00
da632d0787 fix(deps): update module golang.org/x/oauth2 to v0.30.0
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-05 16:25:44 +00:00
f00fbb47cd chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-05 00:32:11 +00:00
f1a09882c5 Add warning about bugs and beta in onboarding
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-02 19:33:36 +02:00
a37f928604 Add a warning that the projet is a MVP and we need feedbacks
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-29 15:21:05 +02:00
dca773455a listmonk: Fix newsletter ID detection
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-29 10:07:30 +02:00
1dac480546 fix(deps): update module github.com/yuin/goldmark to v1.7.11
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-27 12:25:12 +00:00
9a77fbd8cc Revert: fix(deps): update module github.com/gin-contrib/sessions to v1.0.3
All checks were successful
continuous-integration/drone/push Build is passing
Waiting fixes: https://github.com/gin-contrib/sessions/issues/287
2025-04-25 19:26:00 +02:00
838d737f65 Implement DNScontrol record auditor to alert about provider incompatibilities 2025-04-25 18:47:18 +02:00
6cdc6cfdac When the update fails, return concatenated errors instead of nil
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-25 16:02:45 +02:00
df771a3580 Add missing error checking after ImportZone
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-25 15:53:19 +02:00
e3face9c03 Translate "Retour à la zone" 2025-04-25 11:45:16 +02:00
65ad585a7c Keep showing "Stop managing this domain" on errors 2025-04-25 11:42:28 +02:00
afd4e21a4c Fix zone analysis expecting a non-dot ended FQDN in domain search
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-24 16:54:15 +02:00
d45f5792a4 Refresh README 2025-04-23 16:19:39 +02:00
371a702f87 CI: Optimize ARM builds by caching dependancies
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-23 16:07:32 +02:00
7c045ee9ef locale: Keep only the first 2 letters if browser define a dialect 2025-04-23 16:07:32 +02:00
0b6b42351d Can overwrite locale with ?lang=.. in url 2025-04-23 16:07:32 +02:00
830077b8cb Add new translations 2025-04-23 16:07:23 +02:00
305a84dd67 Suggest language change at the beginning of onboarding 2025-04-23 13:51:41 +02:00
f15711a217 fix(deps): update module github.com/coreos/go-oidc/v3 to v3.14.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-23 10:45:35 +00:00
5b772a2d8a fix(deps): update module github.com/miekg/dns to v1.1.65
Some checks are pending
continuous-integration/drone/push Build is pending
2025-04-23 10:45:16 +00:00
02d535e033 Avoid undefined oldZoneId
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-23 12:41:36 +02:00
82f3c700fa Try new renovate config
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-23 11:15:45 +02:00
fc22dc4e47 fix(deps): update module github.com/golang-jwt/jwt/v5 to v5.2.2
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-23 08:14:03 +00:00
64c36c4b60 fix(deps): update module github.com/gin-contrib/sessions to v1.0.3
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-23 08:13:21 +00:00
dc30b80b7c CI: Don't rely on x86-64 hosts for tests and renovate updates + dusting
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-23 10:08:07 +02:00
e8836764ea chore(deps): lock file maintenance
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-23 00:33:43 +00:00
51bf915052 chore(deps): update dependency svelte-check to v4.1.6
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-04-22 21:42:05 +00:00
d93086b594 chore(deps): update dependency vite to v5.4.18
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 21:41:38 +00:00
6e652317fe fix(deps): update module golang.org/x/crypto to v0.37.0
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 21:41:15 +00:00
b0560d0c45 Finish OIDC implementation with new login mechanism
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 23:35:25 +02:00
b25072377d auth: Extract login through session in middleware 2025-04-22 23:35:25 +02:00
4a47b4ba90 session: Don't save common attributes multiple times 2025-04-22 23:35:25 +02:00
a2770334ed fix(deps): update module github.com/ovh/go-ovh to v1.7.0
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 20:20:24 +00:00
5448f4f8df Update module golang.org/x/oauth2 to v0.29.0
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 20:19:49 +00:00
b456549a90 Update dependency prettier to v3.5.3
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 20:18:56 +00:00
77cdc064c6 Update dependency bootstrap to v5.3.5
Some checks are pending
continuous-integration/drone/push Build is running
2025-04-22 15:25:29 +00:00
525 changed files with 11405 additions and 4044 deletions

View file

@ -2,4 +2,4 @@ bindata.go
vendor/
happydomain.db/
happydomain.sock
ui/node_modules/
web/node_modules/

View file

@ -14,7 +14,7 @@ steps:
- apk --no-cache add tar
- yarn config set network-timeout 100000
- yarn --cwd ui install
- tar --transform="s@.@./happydomain-${DRONE_COMMIT}@" --exclude-vcs --exclude=./ui/node_modules/.cache -czf /dev/shm/happydomain-src.tar.gz .
- tar --transform="s@.@./happydomain-${DRONE_COMMIT}@" --exclude-vcs --exclude=./web/node_modules/.cache -czf /dev/shm/happydomain-src.tar.gz .
- mv /dev/shm/happydomain-src.tar.gz .
- yarn --cwd ui --offline build
@ -57,10 +57,10 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain
environment:
CGO_ENABLED: 0
@ -73,10 +73,10 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain
environment:
CGO_ENABLED: 0
@ -84,15 +84,6 @@ steps:
event:
- tag
- name: vet and tests
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- go vet -v ./...
- go test -v ./...
environment:
CGO_ENABLED: 0
- name: deploy
image: plugins/s3
settings:
@ -132,7 +123,7 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOOS: darwin
@ -146,7 +137,7 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOOS: darwin
@ -203,6 +194,9 @@ steps:
from_secret: docker_password
trigger:
branch:
exclude:
- renovate/*
event:
- cron
- push
@ -229,10 +223,10 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain
environment:
CGO_ENABLED: 0
@ -245,10 +239,10 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain
environment:
CGO_ENABLED: 0
@ -256,6 +250,15 @@ steps:
event:
- tag
- name: vet and tests
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- go vet ./...
- go test ./...
environment:
CGO_ENABLED: 0
- name: deploy
image: plugins/s3
settings:
@ -295,7 +298,7 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOOS: darwin
@ -309,7 +312,7 @@ steps:
image: golang:1-alpine
commands:
- apk add --no-cache git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOOS: darwin
@ -392,10 +395,11 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el ./cmd/happyDomain/
- go mod vendor
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOARM: 5
@ -411,10 +415,11 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- sed -i '/npm run build/d' ui/assets.go
- sed -i '/npm run build/d' web/assets.go
- go install github.com/swaggo/swag/cmd/swag@latest
- go generate -v ./...
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el ./cmd/happyDomain/
- go mod vendor
- go generate ./...
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOARM: 5
@ -461,7 +466,7 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOARM: 6
@ -477,7 +482,7 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf ./cmd/happyDomain/
environment:
CGO_ENABLED: 0
GOARM: 6
@ -524,10 +529,10 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- "[ -f docs/docs.go ] || sed -i '/npm run build/d' ui/assets.go"
- "[ -f docs/docs.go ] || sed -i '/npm run build/d' web/assets.go"
- "[ -f docs/docs.go ] || go install github.com/swaggo/swag/cmd/swag@latest"
- "[ -f docs/docs.go ] || go generate -v ./..."
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 ./cmd/happyDomain/
- "[ -f docs/docs.go ] || go generate ./..."
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 happydomain
environment:
CGO_ENABLED: 0
@ -541,7 +546,7 @@ steps:
image: golang:1-alpine
commands:
- apk --no-cache add build-base git
- go build -v -tags netgo,swagger,ui -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 ./cmd/happyDomain/
- go build -tags netgo,swagger,web -ldflags '-w -X "main.Version=${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 ./cmd/happyDomain/
- ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 happydomain
environment:
CGO_ENABLED: 0
@ -609,6 +614,10 @@ trigger:
kind: pipeline
name: docker-manifest
platform:
os: linux
arch: arm64
steps:
- name: publish on Docker Hub
image: plugins/manifest
@ -622,6 +631,9 @@ steps:
from_secret: docker_password
trigger:
branch:
exclude:
- renovate/*
event:
- cron
- push

View file

@ -2,11 +2,11 @@ FROM node:22-alpine AS nodebuild
WORKDIR /go/src/git.happydns.org/happydomain
COPY ui/ ui/
COPY web/ web/
RUN yarn config set network-timeout 100000 && \
yarn --cwd ui install && \
yarn --cwd ui --offline build
yarn --cwd web install && \
yarn --cwd web --offline build
FROM golang:1-alpine AS gobuild
@ -16,22 +16,18 @@ RUN apk add --no-cache git
WORKDIR /go/src/git.happydns.org/happydomain
COPY --from=nodebuild /go/src/git.happydns.org/happydomain/ ./
COPY adapters ./adapters
COPY api ./api
COPY api-admin ./api-admin
COPY cmd ./cmd
COPY generators ./generators
COPY tools ./tools
COPY internal ./internal
COPY model ./model
COPY providers ./providers
COPY services ./services
COPY usecase ./usecase
COPY generate.go go.mod go.sum ./
RUN sed -i '/npm run build/d' ui/assets.go && \
RUN sed -i '/npm run build/d' web/assets.go && \
go install github.com/swaggo/swag/cmd/swag@latest && \
go generate -v ./... && \
go build -v -tags netgo,swagger,ui -ldflags '-w' ./cmd/happyDomain/
go build -v -tags netgo,swagger,web -ldflags '-w' ./cmd/happyDomain/
FROM alpine:3.21

View file

@ -1,7 +1,7 @@
happyDomain
===========
Finally a simple, modern and open source interface for domain name.
happyDomain is a free web application that centralizes the management of your domain names from different registrars and hosts.
![Screenshots of happyDomain](./docs/header.webp)
@ -12,13 +12,19 @@ It runs as a single stateless Linux binary, backed by a database (currently: Lev
* An ultra fast web interface without compromise
* Multiple domains management
* Support for 36+ DNS providers (including dynamic DNS, RFC 2136) thanks to [DNSControl](https://stackexchange.github.io/dnscontrol/)
* Support for 44+ DNS providers (including dynamic DNS, RFC 2136) thanks to [DNSControl](https://stackexchange.github.io/dnscontrol/)
* Support for the most recents resource records thanks to [CoreDNS's library](https://github.com/miekg/dns)
* Zone editor with a diff view to review the changes before propagation
* Keep an history of published changes
* Contextual help
* Multiple user with authentication or one user without authtication
* Compatible with external authentication (through JWT tokens: Auth0, ...)
* Multiple users with authentication or one user without authtication
* Compatible with external authentication (OpenId Connect or through JWT tokens: Auth0, ...)
**happyDomain is functional but still very much a work in progress: it's a carefully crafted proof of concept that evolves thanks to you!**
Given the diversity of DNS configurations and user needs, we haven't yet identified all the bugs. **If something doesn't work, please don't leave: [tell us what's wrong](https://github.com/happyDomain/happydomain/issues).** We're highly responsive and each reported bug helps us improve the tool for everyone.
[Whether it works for you or not, we need your feedback!](https://feedback.happydomain.org/) What do you think of our approach to simplifying domain name management? Your impressions at this stage help us guide the project according to **your actual expectations**.
Using Docker
------------
@ -40,8 +46,8 @@ Building
In order to build the happyDomain project, you'll need the following dependencies:
* `go` at least version 1.21;
* `nodejs` tested with version 20 and 21.
* `go` at least version 1.23;
* `nodejs` tested with version 22.
### Instructions
@ -49,7 +55,7 @@ In order to build the happyDomain project, you'll need the following dependencie
1. First, you'll need to prepare the frontend, by installing the node modules dependencies:
```
pushd ui; npm install; popd
pushd web; npm install; popd
```
2. Then, generate assets files used by Go code:
@ -61,7 +67,7 @@ go generate ./...
3. Finaly, build the Go code:
```
go build -tags listmonk,swagger,ui
go build -tags swagger,web ./cmd/happyDomain
```
This last command will create a binary `happyDomain` you can use standalone.
@ -158,13 +164,13 @@ If you want to contribute to the frontend, instead of regenerating the frontend
In one terminal, run `happydomain` with the following arguments:
```
./happyDomain -dev http://127.0.0.1:8080
./happyDomain -dev http://127.0.0.1:5173
```
In another terminal, run the node part:
```
cd ui; npm run dev
cd web; npm run dev
```
With this setup, static assets integrated inside the go binary will not be used, instead it'll forward all requests for static assets to the node server, that do dynamic reload, etc.

View file

@ -32,9 +32,10 @@ import (
"github.com/earthboundkid/versioninfo/v2"
"github.com/fatih/color"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/app"
"git.happydns.org/happyDomain/internal/config"
_ "git.happydns.org/happyDomain/internal/storage/inmemory"
_ "git.happydns.org/happyDomain/internal/storage/leveldb"
"git.happydns.org/happyDomain/model"
_ "git.happydns.org/happyDomain/services/abstract"
@ -66,7 +67,7 @@ func main() {
color.NoColor = true
// Load and parse options
var opts *config.Options
var opts *happydns.Options
if opts, err = config.ConsolidateConfig(); err != nil {
log.Fatal(err)
}

View file

@ -21,6 +21,6 @@
package main
//go:generate go run generators/gen_icon.go providers providers
//go:generate go run generators/gen_icon.go services svcs
//go:generate go run tools/gen_icon.go providers providers
//go:generate go run tools/gen_icon.go services svcs
//go:generate swag init --generalInfo api/route/route.go

57
go.mod
View file

@ -4,24 +4,24 @@ go 1.23.0
require (
github.com/StackExchange/dnscontrol/v4 v4.3.0
github.com/coreos/go-oidc/v3 v3.12.0
github.com/coreos/go-oidc/v3 v3.14.1
github.com/earthboundkid/versioninfo/v2 v2.24.1
github.com/fatih/color v1.18.0
github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0
github.com/gin-gonic/gin v1.10.1
github.com/go-mail/mail v2.3.1+incompatible
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.4.0
github.com/miekg/dns v1.1.63
github.com/miekg/dns v1.1.66
github.com/mileusna/useragent v1.3.5
github.com/ovh/go-ovh v1.6.0
github.com/ovh/go-ovh v1.7.0
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
github.com/swaggo/swag v1.16.4
github.com/syndtr/goleveldb v1.0.0
github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.31.0
golang.org/x/oauth2 v0.25.0
github.com/yuin/goldmark v1.7.12
golang.org/x/crypto v0.38.0
golang.org/x/oauth2 v0.30.0
)
require (
@ -60,12 +60,11 @@ require (
github.com/aws/smithy-go v1.20.4 // indirect
github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7 // indirect
github.com/cloudflare/cloudflare-go v0.103.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/digitalocean/godo v1.121.0 // indirect
@ -74,10 +73,10 @@ require (
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-gandi/go-gandi v0.7.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@ -86,12 +85,12 @@ require (
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
@ -104,7 +103,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@ -121,7 +120,7 @@ require (
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/oracle/oci-go-sdk/v65 v65.73.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/peterhellberg/link v1.1.0 // indirect
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
@ -135,7 +134,7 @@ require (
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
@ -145,19 +144,19 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/arch v0.16.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
google.golang.org/api v0.195.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect

129
go.sum
View file

@ -75,23 +75,23 @@ github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9/go.mod h1:b
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7 h1:Jk7uhY5q11fE5PlEupX2Lo12w82UhGC6bE1CI5jwFbc=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7/go.mod h1:FnQtD0+Q/1NZxi0eEWN+3ZRyMsE9vzSB3YjyunkbKD0=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.103.0 h1:XXKzgXeUbAo7UTtM4T5wuD2bJPBtNZv7TlZAEy5QI4k=
github.com/cloudflare/cloudflare-go v0.103.0/go.mod h1:0DrjT4g8wgYFYIxhlqR8xi8dNWfyHFGilUkU3+XV8h0=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -123,22 +123,25 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-gandi/go-gandi v0.7.0 h1:gsP33dUspsN1M+ZW9HEgHchK9HiaSkYnltO73RHhSZA=
github.com/go-gandi/go-gandi v0.7.0/go.mod h1:9NoYyfWCjFosClPiWjkbbRK5UViaZ4ctpT8/pKSSFlw=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -169,23 +172,23 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe h1:zn8tqiUbec4wR94o7Qj3LZCAT6uGobhEgnDRg6isG5U=
github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.7.8/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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@ -205,8 +208,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -273,8 +276,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
@ -318,8 +321,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -357,12 +362,12 @@ github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/oracle/oci-go-sdk/v65 v65.73.0 h1:C7uel6CoKk4A1KPkdhFBAyvVyFRTHAmX8m0o64RmfPg=
github.com/oracle/oci-go-sdk/v65 v65.73.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw=
github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
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=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/peterhellberg/link v1.1.0 h1:s2+RH8EGuI/mI4QwrWGSYQCRz7uNgip9BaM04HKu5kc=
github.com/peterhellberg/link v1.1.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8=
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d h1:nf4+lHs8TQeIGFYZMcNg4iQOnZndLfYxnQaKEdqHVA4=
@ -420,8 +425,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@ -449,6 +454,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo=
github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
@ -459,9 +468,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -469,8 +477,10 @@ golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
@ -481,8 +491,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -500,11 +512,15 @@ golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -512,8 +528,10 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -545,8 +563,10 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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=
@ -559,8 +579,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
@ -577,8 +599,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -614,8 +638,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -656,4 +680,3 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -23,6 +23,7 @@ package adapter
import (
"crypto/sha256"
"errors"
"fmt"
"strings"
@ -99,15 +100,21 @@ func NewDNSControlProviderAdapter(configAdapter DNSControlConfigAdapter) (happyd
return nil, err
}
var auditor dnscontrol.RecordAuditor
if p, ok := dnscontrol.DNSProviderTypes[configAdapter.DNSControlName()]; ok && p.RecordAuditor != nil {
auditor = p.RecordAuditor
}
if _, ok := provider.(dnscontrol.ZoneLister); ok {
return &DNSControlAdapterNSProviderWithListZone{DNSControlAdapterNSProvider{provider}}, nil
return &DNSControlAdapterNSProviderWithListZone{DNSControlAdapterNSProvider{provider, auditor}}, nil
} else {
return &DNSControlAdapterNSProvider{provider}, nil
return &DNSControlAdapterNSProvider{provider, auditor}, nil
}
}
type DNSControlAdapterNSProvider struct {
dnscontrol.DNSServiceProvider
DNSServiceProvider dnscontrol.DNSServiceProvider
RecordAuditor dnscontrol.RecordAuditor
}
func (p *DNSControlAdapterNSProvider) CanListZones() bool {
@ -148,6 +155,12 @@ func (p *DNSControlAdapterNSProvider) GetZoneCorrections(domain string, rrs []ha
return
}
errs := p.RecordAuditor(dc.Records)
if errs != nil {
err = fmt.Errorf("some records are incompatibles with this NS provider: %w. Please fix those errors and retry.", errors.Join(errs...))
return
}
defer func() {
if a := recover(); a != nil {
err = fmt.Errorf("%s", a)

View file

@ -28,18 +28,18 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/utils"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/helpers"
"git.happydns.org/happyDomain/internal/usecase/authuser"
"git.happydns.org/happyDomain/model"
)
type AuthUserController struct {
auService happydns.AuthUserUsecase
store storage.Storage
store authuser.AuthUserStorage
}
func NewAuthUserController(auService happydns.AuthUserUsecase, store storage.Storage) *AuthUserController {
func NewAuthUserController(auService happydns.AuthUserUsecase, store authuser.AuthUserStorage) *AuthUserController {
return &AuthUserController{
auService,
store,
@ -62,7 +62,18 @@ func (ac *AuthUserController) AuthUserHandler(c *gin.Context) {
}
func (ac *AuthUserController) GetAuthUsers(c *gin.Context) {
users, err := ac.store.GetAuthUsers()
iter, err := ac.store.ListAllAuthUsers()
if err != nil {
happydns.ApiResponse(c, nil, err)
return
}
defer iter.Close()
var users []*happydns.UserAuth
for iter.Next() {
users = append(users, iter.Item())
}
happydns.ApiResponse(c, users, err)
}
@ -111,13 +122,14 @@ func (ac *AuthUserController) DeleteAuthUser(c *gin.Context) {
func (ac *AuthUserController) EmailValidationLink(c *gin.Context) {
user := c.MustGet("authuser").(*happydns.UserAuth)
happydns.ApiResponse(c, ac.auService.GetValidationLink(user), nil)
happydns.ApiResponse(c, ac.auService.GenerateValidationLink(user), nil)
}
func (ac *AuthUserController) RecoverUserAcct(c *gin.Context) {
user := c.MustGet("authuser").(*happydns.UserAuth)
happydns.ApiResponse(c, ac.auService.GetRecoveryLink(user), nil)
link, err := ac.auService.GenerateRecoveryLink(user)
happydns.ApiResponse(c, link, err)
}
type resetPassword struct {
@ -135,7 +147,7 @@ func (ac *AuthUserController) ResetUserPasswd(c *gin.Context) {
}
if urp.Password == "" {
urp.Password, err = utils.GeneratePassword()
urp.Password, err = helpers.GeneratePassword()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
return

View file

@ -29,18 +29,18 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/storage"
providerUC "git.happydns.org/happyDomain/internal/usecase/provider"
zoneUC "git.happydns.org/happyDomain/internal/usecase/zone"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/usecase"
)
type BackupController struct {
config *config.Options
config *happydns.Options
store storage.Storage
}
func NewBackupController(cfg *config.Options, store storage.Storage) *BackupController {
func NewBackupController(cfg *happydns.Options, store storage.Storage) *BackupController {
return &BackupController{
config: cfg,
store: store,
@ -52,23 +52,30 @@ func (bc *BackupController) DoBackup() (ret happydns.Backup) {
ret.DomainsLogs = map[string][]*happydns.DomainLog{}
// UserAuth
uas, err := bc.store.GetAuthUsers()
uai, err := bc.store.ListAllAuthUsers()
if err != nil {
ret.Errors = append(ret.Errors, fmt.Sprintf("unable to retrieve AuthUsers: %s", err.Error()))
} else {
ret.UsersAuth = uas
defer uai.Close()
for uai.Next() {
ret.UsersAuth = append(ret.UsersAuth, uai.Item())
}
}
// Users
us, err := bc.store.GetUsers()
iter, err := bc.store.ListAllUsers()
if err != nil {
ret.Errors = append(ret.Errors, fmt.Sprintf("unable to retrieve Users: %s", err.Error()))
} else {
ret.Users = us
defer iter.Close()
for iter.Next() {
u := iter.Item()
ret.Users = append(ret.Users, u)
for _, u := range us {
// Domains
ds, err := bc.store.GetDomains(u)
ds, err := bc.store.ListDomains(u)
if err != nil {
ret.Errors = append(ret.Errors, fmt.Sprintf("unable to retrieve Domain names of %s (%s): %s", u.Id.String(), u.Email, err.Error()))
} else {
@ -76,7 +83,7 @@ func (bc *BackupController) DoBackup() (ret happydns.Backup) {
for _, dn := range ds {
// Domain logs
ls, err := bc.store.GetDomainLogs(dn)
ls, err := bc.store.ListDomainLogs(dn)
if err != nil {
ret.Errors = append(ret.Errors, fmt.Sprintf("unable to retrieve domain's logs %s/%s (%s): %s", u.Id.String(), dn.Id.String(), dn.DomainName, err.Error()))
} else {
@ -104,7 +111,7 @@ func (bc *BackupController) DoBackup() (ret happydns.Backup) {
}
// Sessions
ss, err := bc.store.GetUserSessions(u.Id)
ss, err := bc.store.ListUserSessions(u.Id)
if err != nil {
ret.Errors = append(ret.Errors, fmt.Sprintf("unable to retrieve Sessions: %s", err.Error()))
} else {
@ -132,7 +139,7 @@ func (bc *BackupController) DoRestore(backup *happydns.Backup) (errs error) {
// Providers
for _, provider := range backup.Providers {
p, err := usecase.ParseProvider(provider)
p, err := providerUC.ParseProvider(provider)
if err != nil {
errs = errors.Join(errs, err)
}
@ -155,7 +162,7 @@ func (bc *BackupController) DoRestore(backup *happydns.Backup) (errs error) {
// Zones
for _, zmsg := range backup.Zones {
zone, err := usecase.ParseZone(zmsg)
zone, err := zoneUC.ParseZone(zmsg)
if err != nil {
errs = errors.Join(errs, err)
} else {

View file

@ -29,20 +29,24 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/usecase/domain"
"git.happydns.org/happyDomain/model"
)
type DomainController struct {
domainService happydns.DomainUsecase
store storage.Storage
domainService happydns.DomainUsecase
remoteZoneImporter happydns.RemoteZoneImporterUsecase
zoneImporter happydns.ZoneImporterUsecase
store domain.DomainStorage
}
func NewDomainController(duService happydns.DomainUsecase, store storage.Storage) *DomainController {
func NewDomainController(duService happydns.DomainUsecase, remoteZoneImporter happydns.RemoteZoneImporterUsecase, zoneImporter happydns.ZoneImporterUsecase, store domain.DomainStorage) *DomainController {
return &DomainController{
duService,
remoteZoneImporter,
zoneImporter,
store,
}
}
@ -50,26 +54,21 @@ func NewDomainController(duService happydns.DomainUsecase, store storage.Storage
func (dc *DomainController) ListDomains(c *gin.Context) {
user := middleware.MyUser(c)
if user != nil {
apidc := controller.NewDomainController(dc.domainService)
apidc := controller.NewDomainController(dc.domainService, dc.remoteZoneImporter, dc.zoneImporter)
apidc.GetDomains(c)
return
}
var domains happydns.Domains
users, err := dc.store.GetUsers()
iter, err := dc.store.ListAllDomains()
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to retrieve users list: %w", err))
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to retrieve domains list: %w", err))
return
}
for _, user := range users {
usersDomains, err := dc.store.GetDomains(user)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to retrieve %s's domains: %w", user.Email, err))
return
}
defer iter.Close()
domains = append(domains, usersDomains...)
var domains []*happydns.Domain
for iter.Next() {
domains = append(domains, iter.Item())
}
happydns.ApiResponse(c, domains, nil)
@ -85,9 +84,9 @@ func (dc *DomainController) NewDomain(c *gin.Context) {
return
}
ud.Id = nil
ud.IdUser = user.Id
ud.Owner = user.Id
happydns.ApiResponse(c, ud, dc.store.CreateDomain(user, ud))
happydns.ApiResponse(c, ud, dc.store.CreateDomain(ud))
}
func (dc *DomainController) DeleteDomain(c *gin.Context) {
@ -103,35 +102,36 @@ func (dc *DomainController) DeleteDomain(c *gin.Context) {
})
}
domain, err := dc.store.GetDomainByDN(user, c.Param("domain"))
domains, err := dc.store.GetDomainByDN(user, c.Param("domain"))
if err != nil {
middleware.ErrorResponse(c, http.StatusNotFound, err)
return
} else {
domainid = domain.Id
}
if len(domains) != 1 {
middleware.ErrorResponse(c, http.StatusNotFound, fmt.Errorf("too many domains with this FQDN, use domain identifier instead"))
return
}
domainid = domains[0].Id
}
happydns.ApiResponse(c, true, dc.store.DeleteDomain(domainid))
}
func (dc *DomainController) searchUserDomain(filter func(*happydns.Domain) bool) *happydns.User {
users, err := dc.store.GetUsers()
iter, err := dc.store.ListAllDomains()
if err != nil {
log.Println("Unable to retrieve users list:", err.Error())
log.Println("Unable to retrieve domains list:", err.Error())
return nil
}
for _, user := range users {
usersDomains, err := dc.store.GetDomains(user)
if err != nil {
log.Printf("Unable to retrieve %s's domains: %s", user.Email, err.Error())
continue
}
defer iter.Close()
for _, domain := range usersDomains {
if filter(domain) {
return user
}
for iter.Next() {
domain := iter.Item()
if filter(domain) {
// Create a fake minimal user, as only the Id is required to perform further actions on database
return &happydns.User{Id: domain.Owner}
}
}
@ -164,7 +164,17 @@ func (dc *DomainController) GetDomain(c *gin.Context) {
})
}
domain, err := dc.store.GetDomain(user, domainid)
domain, err := dc.store.GetDomain(domainid)
if err != nil {
happydns.ApiResponse(c, nil, err)
return
}
if !user.Id.Equals(domain.Owner) {
happydns.ApiResponse(c, nil, fmt.Errorf("domain not found"))
return
}
happydns.ApiResponse(c, domain, err)
}
}
@ -180,13 +190,6 @@ func (dc *DomainController) UpdateDomain(c *gin.Context) {
}
ud.Id = domain.Id
if !ud.IdUser.Equals(domain.IdUser) {
if err := dc.store.UpdateDomainOwner(domain, &happydns.User{Id: ud.IdUser}); err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
}
happydns.ApiResponse(c, ud, dc.store.UpdateDomain(ud))
}
@ -217,3 +220,15 @@ func (dc *DomainController) ClearDomains(c *gin.Context) {
happydns.ApiResponse(c, true, dc.store.ClearDomains())
}
func (dc *DomainController) UpdateZones(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
err := c.ShouldBindJSON(&domain.ZoneHistory)
if err != nil {
middleware.ErrorResponse(c, http.StatusNotFound, fmt.Errorf("something is wrong in received data: %w", err))
return
}
happydns.ApiResponse(c, domain, dc.store.UpdateDomain(domain))
}

View file

@ -28,18 +28,18 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/usecase/provider"
"git.happydns.org/happyDomain/model"
)
type ProviderController struct {
providerService happydns.ProviderUsecase
store storage.Storage
store provider.ProviderStorage
}
func NewProviderController(providerService happydns.ProviderUsecase, store storage.Storage) *ProviderController {
func NewProviderController(providerService happydns.ProviderUsecase, store provider.ProviderStorage) *ProviderController {
return &ProviderController{
providerService,
store,
@ -54,25 +54,20 @@ func (pc *ProviderController) ListProviders(c *gin.Context) {
return
}
var providers []*happydns.ProviderMeta
users, err := pc.store.GetUsers()
iter, err := pc.store.ListAllProviders()
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to list users: %w", err))
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to list providers: %w", err))
return
}
defer iter.Close()
for _, user := range users {
usersProviders, err := pc.store.ListProviders(user)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to list users: %w", err))
return
}
providers = append(providers, usersProviders.Metas()...)
var res []*happydns.ProviderMeta
for iter.Next() {
provider := iter.Item()
res = append(res, &provider.ProviderMeta)
}
happydns.ApiResponse(c, providers, nil)
happydns.ApiResponse(c, res, nil)
}
func (pc *ProviderController) AddProvider(c *gin.Context) {

View file

@ -27,27 +27,26 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/api/middleware"
serviceUC "git.happydns.org/happyDomain/internal/usecase/service"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/usecase"
)
type ServiceController struct {
serviceService happydns.ServiceUsecase
zoneService happydns.ZoneUsecase
store storage.Storage
zoneServiceUC happydns.ZoneServiceUsecase
}
func NewServiceController(serviceService happydns.ServiceUsecase, zoneService happydns.ZoneUsecase, store storage.Storage) *ServiceController {
func NewServiceController(serviceService happydns.ServiceUsecase, zoneServiceUC happydns.ZoneServiceUsecase) *ServiceController {
return &ServiceController{
serviceService,
zoneService,
store,
zoneServiceUC,
}
}
func (sc *ServiceController) DeleteZoneService(c *gin.Context) {
user := middleware.MyUser(c)
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
serviceid := c.MustGet("serviceid").(happydns.Identifier)
@ -57,7 +56,7 @@ func (sc *ServiceController) DeleteZoneService(c *gin.Context) {
return
}
err := sc.zoneService.DeleteService(zone, subdomain, serviceid)
zone, err := sc.zoneServiceUC.RemoveServiceFromZone(user, domain, zone, subdomain, serviceid)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -76,6 +75,8 @@ func (sc *ServiceController) GetZoneService(c *gin.Context) {
}
func (sc *ServiceController) UpdateZoneService(c *gin.Context) {
user := middleware.MyUser(c)
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
serviceid := c.MustGet("serviceid").(happydns.Identifier)
@ -86,7 +87,7 @@ func (sc *ServiceController) UpdateZoneService(c *gin.Context) {
return
}
newservice, err := usecase.ParseService(&usc)
newservice, err := serviceUC.ParseService(&usc)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
@ -97,7 +98,7 @@ func (sc *ServiceController) UpdateZoneService(c *gin.Context) {
return
}
err = sc.zoneService.UpdateService(zone, usc.Domain, usc.Id, newservice)
zone, err = sc.zoneServiceUC.UpdateZoneService(user, domain, zone, happydns.Subdomain(usc.Domain), usc.Id, newservice)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -26,17 +26,16 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/usecase/session"
"git.happydns.org/happyDomain/model"
)
type SessionController struct {
config *config.Options
store storage.Storage
config *happydns.Options
store session.SessionStorage
}
func NewSessionController(cfg *config.Options, store storage.Storage) *SessionController {
func NewSessionController(cfg *happydns.Options, store session.SessionStorage) *SessionController {
return &SessionController{
config: cfg,
store: store,

View file

@ -0,0 +1,42 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/model"
)
type TidyController struct {
tidyUpService happydns.TidyUpUseCase
}
func NewTidyController(tidyUpService happydns.TidyUpUseCase) *TidyController {
return &TidyController{
tidyUpService: tidyUpService,
}
}
func (tc *TidyController) TidyDB(c *gin.Context) {
happydns.ApiResponse(c, true, tc.tidyUpService.TidyAll())
}

View file

@ -27,17 +27,17 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/usecase/user"
"git.happydns.org/happyDomain/model"
)
type UserController struct {
userService happydns.UserUsecase
store storage.Storage
store user.UserStorage
}
func NewUserController(store storage.Storage, userService happydns.UserUsecase) *UserController {
func NewUserController(store user.UserStorage, userService happydns.UserUsecase) *UserController {
return &UserController{
userService,
store,
@ -60,7 +60,17 @@ func (uc *UserController) UserHandler(c *gin.Context) {
}
func (uc *UserController) GetUsers(c *gin.Context) {
users, err := uc.store.GetUsers()
iter, err := uc.store.ListAllUsers()
if err != nil {
happydns.ApiResponse(c, nil, err)
return
}
var users []*happydns.User
for iter.Next() {
users = append(users, iter.Item())
}
happydns.ApiResponse(c, users, err)
}
@ -104,7 +114,3 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
happydns.ApiResponse(c, true, uc.store.DeleteUser(user.Id))
}
func (uc *UserController) TidyDB(c *gin.Context) {
happydns.ApiResponse(c, true, uc.store.Tidy())
}

View file

@ -27,22 +27,24 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/usecase/zone"
"git.happydns.org/happyDomain/model"
)
type ZoneController struct {
domainService happydns.DomainUsecase
zoneService happydns.ZoneUsecase
store storage.Storage
domainService happydns.DomainUsecase
zoneService happydns.ZoneUsecase
zoneCorrectionService happydns.ZoneCorrectionApplierUsecase
store zone.ZoneStorage
}
func NewZoneController(domainService happydns.DomainUsecase, zoneService happydns.ZoneUsecase, store storage.Storage) *ZoneController {
func NewZoneController(domainService happydns.DomainUsecase, zoneService happydns.ZoneUsecase, zoneCorrectionService happydns.ZoneCorrectionApplierUsecase, store zone.ZoneStorage) *ZoneController {
return &ZoneController{
domainService,
zoneService,
zoneCorrectionService,
store,
}
}
@ -70,7 +72,7 @@ func (zc *ZoneController) DeleteZone(c *gin.Context) {
}
func (zc *ZoneController) GetZone(c *gin.Context) {
apizc := controller.NewZoneController(zc.zoneService, zc.domainService)
apizc := controller.NewZoneController(zc.zoneService, zc.domainService, zc.zoneCorrectionService)
apizc.GetZone(c)
}
@ -93,15 +95,3 @@ func (zc *ZoneController) UpdateZone(c *gin.Context) {
happydns.ApiResponse(c, uz, zc.store.UpdateZone(uz))
}
func (zc *ZoneController) UpdateZones(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
err := c.ShouldBindJSON(&domain.ZoneHistory)
if err != nil {
middleware.ErrorResponse(c, http.StatusNotFound, fmt.Errorf("something is wrong in received data: %w", err))
return
}
happydns.ApiResponse(c, domain, zc.store.UpdateDomain(domain))
}

View file

@ -24,13 +24,13 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareUserAuthsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
ac := controller.NewAuthUserController(dependancies.GetAuthUserService(), store)
ac := controller.NewAuthUserController(dependancies.AuthUserUsecase(), store)
router.GET("/auth", ac.GetAuthUsers)
router.POST("/auth", ac.NewAuthUser)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareBackupRoutes(cfg *config.Options, router *gin.RouterGroup, store storage.Storage) {
func declareBackupRoutes(cfg *happydns.Options, router *gin.RouterGroup, store storage.Storage) {
bc := controller.NewBackupController(cfg, store)
router.POST("/backup.json", bc.BackupJSON)

View file

@ -24,14 +24,19 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareDomainRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
dc := controller.NewDomainController(dependancies.GetDomainService(), store)
dc := controller.NewDomainController(
dependancies.DomainUsecase(),
dependancies.RemoteZoneImporterUsecase(),
dependancies.ZoneImporterUsecase(),
store,
)
router.GET("/domains", dc.ListDomains)
router.POST("/domains", dc.NewDomain)
@ -40,10 +45,11 @@ func declareDomainRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseD
router.DELETE("/domains/:domain", dc.DeleteDomain)
apiDomainsRoutes := router.Group("/domains/:domain")
apiDomainsRoutes.Use(middleware.DomainHandler(dependancies.GetDomainService(), true))
apiDomainsRoutes.Use(middleware.DomainHandler(dependancies.DomainUsecase(), true))
apiDomainsRoutes.GET("", dc.GetDomain)
apiDomainsRoutes.PUT("", dc.UpdateDomain)
apiDomainsRoutes.PUT("/zones", dc.UpdateZones)
declareZoneRoutes(apiDomainsRoutes, dependancies, store)
}

View file

@ -24,27 +24,27 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareProviderRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
pc := controller.NewProviderController(dependancies.GetProviderService(false), store)
pc := controller.NewProviderController(dependancies.ProviderUsecase(false), store)
router.GET("/providers", pc.ListProviders)
router.POST("/providers", pc.AddProvider)
router.DELETE("/providers", pc.ClearProviders)
apiProvidersMetaRoutes := router.Group("/providers/:pid")
apiProvidersMetaRoutes.Use(middleware.ProviderMetaHandler(dependancies.GetProviderService(false)))
apiProvidersMetaRoutes.Use(middleware.ProviderMetaHandler(dependancies.ProviderUsecase(false)))
apiProvidersMetaRoutes.PUT("", pc.UpdateProvider)
apiProvidersMetaRoutes.DELETE("", pc.DeleteProvider)
apiProvidersRoutes := router.Group("/providers/:pid")
apiProvidersRoutes.Use(middleware.ProviderHandler(dependancies.GetProviderService(false)))
apiProvidersRoutes.Use(middleware.ProviderHandler(dependancies.ProviderUsecase(false)))
apiProvidersRoutes.GET("", pc.GetProvider)

View file

@ -24,13 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
api "git.happydns.org/happyDomain/api/route"
"git.happydns.org/happyDomain/internal/config"
api "git.happydns.org/happyDomain/internal/api/route"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func DeclareRoutes(cfg *config.Options, router *gin.Engine, s storage.Storage, dependancies happydns.UsecaseDependancies) {
func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, s storage.Storage, dependancies happydns.UsecaseDependancies) {
apiRoutes := router.Group("/api")
declareBackupRoutes(cfg, apiRoutes, s)
@ -39,5 +38,6 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine, s storage.Storage, d
declareSessionsRoutes(cfg, apiRoutes, s)
declareUserAuthsRoutes(apiRoutes, dependancies, s)
declareUsersRoutes(apiRoutes, dependancies, s)
declareTidyRoutes(apiRoutes, s)
api.DeclareVersionRoutes(apiRoutes)
}

View file

@ -24,17 +24,20 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareZoneServiceRoutes(apiZonesRoutes *gin.RouterGroup, zc *controller.ZoneController, dependancies happydns.UsecaseDependancies, store storage.Storage) {
sc := controller.NewServiceController(dependancies.GetServiceService(), dependancies.GetZoneService(), store)
sc := controller.NewServiceController(
dependancies.ServiceUsecase(),
dependancies.ZoneServiceUsecase(),
)
apiZonesServiceIdRoutes := apiZonesRoutes.Group("/services/:serviceid")
apiZonesServiceIdRoutes.Use(middleware.ServiceIdHandler(dependancies.GetServiceService()))
apiZonesServiceIdRoutes.Use(middleware.ServiceIdHandler(dependancies.ServiceUsecase()))
apiZonesServiceIdRoutes.GET("", sc.GetZoneService)
apiZonesServiceIdRoutes.PUT("", sc.UpdateZoneService)
apiZonesServiceIdRoutes.DELETE("", sc.DeleteZoneService)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareSessionsRoutes(cfg *config.Options, router *gin.RouterGroup, store storage.Storage) {
func declareSessionsRoutes(cfg *happydns.Options, router *gin.RouterGroup, store storage.Storage) {
sc := controller.NewSessionController(cfg, store)
router.DELETE("/sessions", sc.DeleteSessions)

View file

@ -0,0 +1,36 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/usecase"
)
func declareTidyRoutes(router *gin.RouterGroup, store storage.Storage) {
tc := controller.NewTidyController(usecase.NewTidyUpUsecase(store))
router.POST("/tidy", tc.TidyDB)
}

View file

@ -24,13 +24,13 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareUsersRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
sc := controller.NewUserController(store, dependancies.GetUserService())
sc := controller.NewUserController(store, dependancies.UserUsecase())
router.GET("/users", sc.GetUsers)
router.POST("/users", sc.NewUser)
@ -45,6 +45,4 @@ func declareUsersRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDe
declareDomainRoutes(apiUsersRoutes, dependancies, store)
declareProviderRoutes(apiUsersRoutes, dependancies, store)
router.POST("/tidy", sc.TidyDB)
}

View file

@ -24,23 +24,28 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api-admin/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declareZoneRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
zc := controller.NewZoneController(dependancies.GetDomainService(), dependancies.GetZoneService(), store)
zc := controller.NewZoneController(
dependancies.DomainUsecase(),
dependancies.ZoneUsecase(),
dependancies.ZoneCorrectionApplierUsecase(),
store,
)
router.GET("/zones", zc.ListZones)
router.PUT("/zones", zc.UpdateZones)
router.POST("/zones", zc.AddZone)
// PUT /zones is handled by DomainController
router.DELETE("/zones/:zoneid", zc.DeleteZone)
apiZonesRoutes := router.Group("/zones/:zoneid")
apiZonesRoutes.Use(middleware.ZoneHandler(dependancies.GetZoneService()))
apiZonesRoutes.Use(middleware.ZoneHandler(dependancies.ZoneUsecase()))
apiZonesRoutes.GET("", zc.GetZone)
apiZonesRoutes.PUT("", zc.UpdateZone)

View file

@ -29,6 +29,7 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -63,18 +64,7 @@ func (lc *LoginController) Login(c *gin.Context) {
return
}
session := sessions.Default(c)
session.Clear()
session.Set("iduser", user.Id)
err = session.Save()
if err != nil {
log.Printf("%s: unable to save user session: %s", c.ClientIP(), err.Error())
c.JSON(http.StatusUnauthorized, happydns.ErrorResponse{Message: "Invalid username or password."})
return
}
log.Printf("%s: now logged as %q\n", c.ClientIP(), user.Email)
middleware.SessionLoginOK(c, user)
c.JSON(http.StatusOK, user)
}

View file

@ -31,6 +31,7 @@ import (
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
@ -38,7 +39,7 @@ import (
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -47,23 +48,39 @@ const (
)
type OIDCProvider struct {
config *config.Options
config *happydns.Options
authService happydns.AuthenticationUsecase
oauth2config *oauth2.Config
oidcVerifier *oidc.IDTokenVerifier
}
func NewOIDCProvider(cfg *config.Options, authService happydns.AuthenticationUsecase) *OIDCProvider {
func GetOIDCProvider(o *happydns.Options, ctx context.Context) (*oidc.Provider, error) {
return oidc.NewProvider(ctx, strings.TrimSuffix(o.OIDCClients[0].ProviderURL.String(), "/.well-known/openid-configuration"))
}
func GetOAuth2Config(o *happydns.Options, provider *oidc.Provider) *oauth2.Config {
oauth2Config := oauth2.Config{
ClientID: o.OIDCClients[0].ClientID,
ClientSecret: o.OIDCClients[0].ClientSecret,
RedirectURL: o.GetAuthURL().String(),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
return &oauth2Config
}
func NewOIDCProvider(cfg *happydns.Options, authService happydns.AuthenticationUsecase) *OIDCProvider {
// Initialize OIDC
provider, err := cfg.GetOIDCProvider(context.Background())
provider, err := GetOIDCProvider(cfg, context.Background())
if err != nil {
log.Fatal("Unable to instantiate OIDC Provider:", err)
}
oauth2Config := cfg.GetOAuth2Config(provider)
oauth2Config := GetOAuth2Config(cfg, provider)
oidcVerifier := provider.Verifier(&oidc.Config{
ClientID: config.OIDCClientID,
ClientID: cfg.OIDCClients[0].ClientID,
})
return &OIDCProvider{
@ -87,7 +104,6 @@ func (p *OIDCProvider) RedirectOIDC(c *gin.Context) {
session.Set(SESSION_KEY_OIDC_STATE, hex.EncodeToString(state))
err = session.Save()
if err != nil {
log.Println("Unable to redirect_OIDC, session.Save fails:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: "Sorry, we are currently unable to respond to your request. Please retry later."})
@ -108,6 +124,14 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) {
return
}
session.Delete(SESSION_KEY_OIDC_STATE)
err := session.Save()
if err != nil {
log.Println("Unable to CompleteOIDC, session.Save fails:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: "Sorry, we are currently unable to respond to your request. Please retry later."})
return
}
oauth2Token, err := p.oauth2config.Exchange(c, c.Query("code"))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: fmt.Sprintf("Failed to exchange token: %s", err.Error())})
@ -157,5 +181,7 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) {
return
}
middleware.SessionLoginOK(c, &profile)
c.Redirect(http.StatusFound, p.config.GetBaseURL()+"/")
}

View file

@ -29,17 +29,21 @@ import (
"github.com/gin-gonic/gin"
"github.com/miekg/dns"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
type DomainController struct {
domainService happydns.DomainUsecase
domainService happydns.DomainUsecase
remoteZoneImporter happydns.RemoteZoneImporterUsecase
zoneImporter happydns.ZoneImporterUsecase
}
func NewDomainController(domainService happydns.DomainUsecase) *DomainController {
func NewDomainController(domainService happydns.DomainUsecase, remoteZoneImporter happydns.RemoteZoneImporterUsecase, zoneImporter happydns.ZoneImporterUsecase) *DomainController {
return &DomainController{
domainService: domainService,
domainService: domainService,
remoteZoneImporter: remoteZoneImporter,
zoneImporter: zoneImporter,
}
}
@ -80,7 +84,7 @@ func (dc *DomainController) GetDomains(c *gin.Context) {
// @Tags domains
// @Accept json
// @Produce json
// @Param body body happydns.DomainMinimal true "Domain object that you want to manage through happyDomain."
// @Param body body happydns.DomainCreationInput true "Domain object that you want to manage through happyDomain."
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.Domain
// @Failure 400 {object} happydns.ErrorResponse "Error in received data"
@ -230,7 +234,7 @@ func (dc *DomainController) RetrieveZone(c *gin.Context) {
}
domain := c.MustGet("domain").(*happydns.Domain)
zone, err := dc.domainService.RetrieveRemoteZone(user, domain)
zone, err := dc.remoteZoneImporter.Import(user, domain)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -263,7 +267,7 @@ func (dc *DomainController) ImportZone(c *gin.Context) {
rrs = append(rrs, rr)
}
zone, err := dc.domainService.ImportZone(user, domain, rrs)
zone, err := dc.zoneImporter.Import(user, domain, rrs)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -58,7 +58,7 @@ func NewDomainLogController(domainLogService happydns.DomainLogUsecase) *DomainL
func (dlc *DomainLogController) GetDomainLogs(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
logs, err := dlc.domainLogService.GetDomainLogs(domain)
logs, err := dlc.domainLogService.ListDomainLogs(domain)
if err != nil {
log.Printf("%s: An error occurs in GetDomainLogs, when retrieving logs: %s", c.ClientIP(), err.Error())

View file

@ -27,7 +27,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)

View file

@ -28,7 +28,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -67,6 +67,7 @@ func (psc *ProviderSettingsController) NextProviderSettingsState(c *gin.Context)
}
specs := c.MustGet("providerspecs").(happydns.ProviderBody)
pType := c.MustGet("providertype").(string)
var uss happydns.ProviderSettingsState
uss.ProviderBody = specs
@ -77,7 +78,7 @@ func (psc *ProviderSettingsController) NextProviderSettingsState(c *gin.Context)
return
}
provider, form, err := psc.pSettingsServices.NextProviderSettingsState(specs, user, &uss)
provider, form, err := psc.pSettingsServices.NextProviderSettingsState(&uss, pType, user)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -26,7 +26,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)

View file

@ -28,7 +28,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)

View file

@ -28,18 +28,18 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
serviceUC "git.happydns.org/happyDomain/internal/usecase/service"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/usecase"
)
type ServiceController struct {
duService happydns.DomainUsecase
suService happydns.ServiceUsecase
duService happydns.ZoneServiceUsecase
zuService happydns.ZoneUsecase
}
func NewServiceController(duService happydns.DomainUsecase, suService happydns.ServiceUsecase, zuService happydns.ZoneUsecase) *ServiceController {
func NewServiceController(duService happydns.ZoneServiceUsecase, suService happydns.ServiceUsecase, zuService happydns.ZoneUsecase) *ServiceController {
return &ServiceController{
duService: duService,
suService: suService,
@ -68,7 +68,7 @@ func (sc *ServiceController) AddZoneService(c *gin.Context) {
user := middleware.MyUser(c)
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
subdomain := c.MustGet("subdomain").(string)
subdomain := c.MustGet("subdomain").(happydns.Subdomain)
var usc happydns.ServiceMessage
err := c.ShouldBindJSON(&usc)
@ -78,13 +78,13 @@ func (sc *ServiceController) AddZoneService(c *gin.Context) {
return
}
newservice, err := usecase.ParseService(&usc)
newservice, err := serviceUC.ParseService(&usc)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
zone, err = sc.duService.AppendZoneService(user, domain, zone, subdomain, domain.DomainName, newservice)
zone, err = sc.duService.AddServiceToZone(user, domain, zone, subdomain, happydns.Origin(domain.DomainName), newservice)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -113,7 +113,7 @@ func (sc *ServiceController) AddZoneService(c *gin.Context) {
func (sc *ServiceController) GetZoneService(c *gin.Context) {
zone := c.MustGet("zone").(*happydns.Zone)
serviceid := c.MustGet("serviceid").(happydns.Identifier)
subdomain := c.MustGet("subdomain").(string)
subdomain := c.MustGet("subdomain").(happydns.Subdomain)
_, svc := zone.FindSubdomainService(subdomain, serviceid)
@ -141,7 +141,7 @@ func (sc *ServiceController) GetServiceRecords(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
serviceid := c.MustGet("serviceid").(happydns.Identifier)
subdomain := c.MustGet("subdomain").(string)
subdomain := c.MustGet("subdomain").(happydns.Subdomain)
_, svc := zone.FindSubdomainService(subdomain, serviceid)
if svc == nil {
@ -192,13 +192,13 @@ func (sc *ServiceController) UpdateZoneService(c *gin.Context) {
return
}
newservice, err := usecase.ParseService(&usc)
newservice, err := serviceUC.ParseService(&usc)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
zone, err = sc.duService.UpdateZoneService(user, domain, zone, usc.Domain, usc.Id, newservice)
zone, err = sc.duService.UpdateZoneService(user, domain, zone, happydns.Subdomain(usc.Domain), usc.Id, newservice)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -230,9 +230,9 @@ func (sc *ServiceController) DeleteZoneService(c *gin.Context) {
domain := c.MustGet("domain").(*happydns.Domain)
zone := c.MustGet("zone").(*happydns.Zone)
serviceid := c.MustGet("serviceid").(happydns.Identifier)
subdomain := c.MustGet("subdomain").(string)
subdomain := c.MustGet("subdomain").(happydns.Subdomain)
zone, err := sc.duService.DeleteZoneService(user, domain, zone, subdomain, serviceid)
zone, err := sc.duService.RemoveServiceFromZone(user, domain, zone, subdomain, serviceid)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -27,7 +27,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)

View file

@ -29,7 +29,7 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -129,7 +129,7 @@ func (sc *SessionController) ClearUserSessions(c *gin.Context) {
return
}
err := sc.sessionService.ClearUserSessions(myuser)
err := sc.sessionService.CloseUserSessions(myuser)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -157,7 +157,7 @@ func (sc *SessionController) GetSessions(c *gin.Context) {
return
}
s, err := sc.sessionService.GetUserSessions(myuser)
s, err := sc.sessionService.ListUserSessions(myuser)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -28,7 +28,7 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -138,14 +138,6 @@ func (rc *UserRecoveryController) ValidateUserAddress(c *gin.Context) {
c.Status(http.StatusNoContent)
}
type UploadedAccountRecovery struct {
// Key is the secret sent by email to the user.
Key string
// Password is the new password to use with this account.
Password string
}
// recoverUserAccount performs account recovery by reseting the password of the account.
//
// @Summary Reset password with link in email.
@ -163,7 +155,7 @@ type UploadedAccountRecovery struct {
func (rc *UserRecoveryController) RecoverUserAccount(c *gin.Context) {
user := c.MustGet("authuser").(*happydns.UserAuth)
var uar UploadedAccountRecovery
var uar happydns.AccountRecoveryForm
err := c.ShouldBindJSON(&uar)
if err != nil {
log.Printf("%s sends invalid AccountRecovey JSON: %s", c.ClientIP(), err.Error())
@ -171,17 +163,7 @@ func (rc *UserRecoveryController) RecoverUserAccount(c *gin.Context) {
return
}
if err := user.CanRecoverAccount(uar.Key); err != nil {
c.AbortWithStatusJSON(http.StatusForbidden, happydns.ErrorResponse{Message: err.Error()})
return
}
if len(uar.Password) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, happydns.ErrorResponse{Message: "Password can't be empty!"})
return
}
if err := rc.auService.ChangePassword(user, uar.Password); err != nil {
if err := rc.auService.ResetPassword(user, uar); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, happydns.ErrorResponse{Message: err.Error()})
return
}

View file

@ -28,19 +28,21 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
type ZoneController struct {
domainService happydns.DomainUsecase
zoneService happydns.ZoneUsecase
domainService happydns.DomainUsecase
zoneCorrectionService happydns.ZoneCorrectionApplierUsecase
zoneService happydns.ZoneUsecase
}
func NewZoneController(zoneService happydns.ZoneUsecase, domainService happydns.DomainUsecase) *ZoneController {
func NewZoneController(zoneService happydns.ZoneUsecase, domainService happydns.DomainUsecase, zoneCorrectionService happydns.ZoneCorrectionApplierUsecase) *ZoneController {
return &ZoneController{
domainService: domainService,
zoneService: zoneService,
domainService: domainService,
zoneCorrectionService: zoneCorrectionService,
zoneService: zoneService,
}
}
@ -68,7 +70,7 @@ func (zc *ZoneController) GetZone(c *gin.Context) {
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain} [get]
func (zc *ZoneController) GetZoneSubdomain(c *gin.Context) {
zone := c.MustGet("zone").(*happydns.Zone)
subdomain := c.MustGet("subdomain").(string)
subdomain := c.MustGet("subdomain").(happydns.Subdomain)
c.JSON(http.StatusOK, happydns.ZoneServices{
Services: zone.Services[subdomain],
@ -102,7 +104,7 @@ func (zc *ZoneController) DiffZones(c *gin.Context) {
var corrections []*happydns.Correction
if c.Param("oldzoneid") == "@" {
var err error
corrections, err = zc.zoneService.GetZoneCorrections(user, domain, newzone)
corrections, err = zc.zoneCorrectionService.List(user, domain, newzone)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
@ -155,7 +157,7 @@ func (zc *ZoneController) ApplyZoneCorrections(c *gin.Context) {
return
}
newZone, err := zc.domainService.ApplyZoneCorrection(user, domain, zone, &form)
newZone, err := zc.zoneCorrectionService.Apply(user, domain, zone, &form)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return

View file

@ -0,0 +1,61 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package middleware
import (
"fmt"
"log"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/model"
)
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if _, ok := c.Get("LoggedUser"); !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, happydns.ErrorResponse{Message: "Please login to access this resource.", Link: "/login"})
return
}
c.Next()
}
}
func SessionLoginOK(c *gin.Context, user happydns.UserInfo) error {
session := sessions.Default(c)
session.Clear()
session.Set("iduser", user.GetUserId())
err := session.Save()
if err != nil {
return happydns.InternalError{
Err: fmt.Errorf("failed to save save user session: %s", err),
UserMessage: "Invalid username or password.",
}
}
log.Printf("%s: now logged as %q\n", c.ClientIP(), user.GetEmail())
return nil
}

View file

@ -48,11 +48,19 @@ func DomainHandler(domainService happydns.DomainUsecase, allowFQDN bool) gin.Han
return
}
domain, err = domainService.GetUserDomainByFQDN(user, c.Param("domain"))
if err != nil {
var domains []*happydns.Domain
domains, err = domainService.GetUserDomainByFQDN(user, c.Param("domain"))
if err != nil || len(domains) == 0 {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Domain not found"})
return
}
if len(domains) != 1 {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "There are many domain names with this FQDN in your account, please use their ID to access it instead"})
return
}
domain = domains[0]
} else {
domain, err = domainService.GetUserDomain(user, dnid)
if err != nil {
@ -68,7 +76,7 @@ func DomainHandler(domainService happydns.DomainUsecase, allowFQDN bool) gin.Han
} else if src, exists := c.Get("providermeta"); exists {
provider = src.(*happydns.ProviderMeta)
}
if provider != nil && !provider.Id.Equals(domain.IdProvider) {
if provider != nil && !provider.Id.Equals(domain.ProviderId) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Domain not found (not child of provider)"})
return
}

View file

@ -0,0 +1,63 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package middleware
import (
"errors"
"log"
"net/http"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/model"
)
func ErrorResponse(c *gin.Context, defaultStatus int, err error) {
if ie, ok := err.(happydns.InternalError); ok {
log.Println(ie.Error())
status := ie.HTTPStatus()
if status == 0 {
status = http.StatusInternalServerError
}
c.AbortWithStatusJSON(status, ie.ToErrorResponse())
return
} else if e, ok := err.(happydns.HTTPError); ok {
status := e.HTTPStatus()
if status == 0 {
status = http.StatusInternalServerError
}
c.AbortWithStatusJSON(status, ie.ToErrorResponse())
return
} else if errors.Is(err, happydns.ErrAuthUserNotFound) || errors.Is(err, happydns.ErrDomainNotFound) || errors.Is(err, happydns.ErrDomainLogNotFound) || errors.Is(err, happydns.ErrProviderNotFound) || errors.Is(err, happydns.ErrSessionNotFound) || errors.Is(err, happydns.ErrUserNotFound) || errors.Is(err, happydns.ErrUserAlreadyExist) || errors.Is(err, happydns.ErrZoneNotFound) {
c.AbortWithStatusJSON(http.StatusNotFound, happydns.ErrorResponse{
Message: err.Error(),
})
return
}
c.AbortWithStatusJSON(defaultStatus, happydns.ErrorResponse{
Message: err.Error(),
})
}

View file

@ -39,6 +39,7 @@ func ProviderSpecsHandler(c *gin.Context) {
return
}
c.Set("providertype", psid)
c.Set("providerspecs", pbody)
c.Next()

View file

@ -34,10 +34,7 @@ import (
func ParseZoneId(c *gin.Context, paramName string) (happydns.Identifier, error) {
zoneid, err := happydns.NewIdentifierFromString(c.Param(paramName))
if err != nil {
return nil, happydns.InternalError{
Err: fmt.Errorf("bad zone identifier (%s) format: %s", paramName, err.Error()),
HTTPStatus: http.StatusBadRequest,
}
return nil, happydns.ValidationError{Msg: fmt.Sprintf("bad zone identifier format (%s): %s", paramName, err.Error())}
}
return zoneid, nil
@ -69,7 +66,7 @@ func SubdomainHandler(c *gin.Context) {
subdomain := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(c.Param("subdomain"), "."+domain.DomainName), "@"), domain.DomainName)
c.Set("subdomain", subdomain)
c.Set("subdomain", happydns.Subdomain(subdomain))
c.Next()
}

View file

@ -26,16 +26,16 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareAuthUserRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, lc *controller.LoginController) *controller.AuthUserController {
ac := controller.NewAuthUserController(dependancies.GetAuthUserService(), lc)
ac := controller.NewAuthUserController(dependancies.AuthUserUsecase(), lc)
apiUserAuthRoutes := router.Group("/users/:uid")
apiUserAuthRoutes.Use(middleware.AuthUserHandler(dependancies.GetAuthUserService()))
apiUserAuthRoutes.Use(middleware.AuthUserHandler(dependancies.AuthUserUsecase()))
apiUserAuthRoutes.GET("/is_auth_user", func(c *gin.Context) {
c.Status(http.StatusNoContent)
})

View file

@ -28,23 +28,22 @@ import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareAuthenticationRoutes(cfg *config.Options, baserouter, apirouter *gin.RouterGroup, dependancies happydns.UsecaseDependancies) *controller.LoginController {
lc := controller.NewLoginController(dependancies.GetAuthenticationService())
func DeclareAuthenticationRoutes(cfg *happydns.Options, baserouter, apirouter *gin.RouterGroup, dependancies happydns.UsecaseDependancies) *controller.LoginController {
lc := controller.NewLoginController(dependancies.AuthenticationUsecase())
apirouter.POST("/auth", lc.Login)
apirouter.POST("/auth/logout", lc.Logout)
if cfg.GetOIDCProviderURL() != "" {
oidcp := controller.NewOIDCProvider(cfg, dependancies.GetAuthenticationService())
if len(cfg.OIDCClients) > 0 {
oidcp := controller.NewOIDCProvider(cfg, dependancies.AuthenticationUsecase())
authRoutes := baserouter.Group("/auth")
providerurl, _ := url.Parse(cfg.GetOIDCProviderURL())
providerurl, _ := url.Parse(cfg.OIDCClients[0].ProviderURL.String())
authRoutes.GET("has_oidc", func(c *gin.Context) {
parts := strings.Split(strings.TrimSuffix(providerurl.Host, "."), ".")
if len(parts) > 2 {

View file

@ -24,19 +24,23 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareDomainRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
dc := controller.NewDomainController(dependancies.GetDomainService())
dc := controller.NewDomainController(
dependancies.DomainUsecase(),
dependancies.RemoteZoneImporterUsecase(),
dependancies.ZoneImporterUsecase(),
)
router.GET("/domains", dc.GetDomains)
router.POST("/domains", dc.AddDomain)
apiDomainsRoutes := router.Group("/domains/:domain")
apiDomainsRoutes.Use(middleware.DomainHandler(dependancies.GetDomainService(), false))
apiDomainsRoutes.Use(middleware.DomainHandler(dependancies.DomainUsecase(), false))
apiDomainsRoutes.GET("", dc.GetDomain)
apiDomainsRoutes.PUT("", dc.UpdateDomain)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareDomainLogRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
dlc := controller.NewDomainLogController(dependancies.GetDomainLogService())
dlc := controller.NewDomainLogController(dependancies.DomainLogUsecase())
router.GET("/logs", dlc.GetDomainLogs)
}

View file

@ -24,24 +24,24 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareProviderRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
pc := controller.NewProviderController(dependancies.GetProviderService(true))
pc := controller.NewProviderController(dependancies.ProviderUsecase(true))
router.GET("/providers", pc.ListProviders)
router.POST("/providers", pc.AddProvider)
apiProvidersMetaRoutes := router.Group("/providers/:pid")
apiProvidersMetaRoutes.Use(middleware.ProviderMetaHandler(dependancies.GetProviderService(true)))
apiProvidersMetaRoutes.Use(middleware.ProviderMetaHandler(dependancies.ProviderUsecase(true)))
apiProvidersMetaRoutes.DELETE("", pc.DeleteProvider)
apiProviderRoutes := router.Group("/providers/:pid")
apiProviderRoutes.Use(middleware.ProviderHandler(dependancies.GetProviderService(true)))
apiProviderRoutes.Use(middleware.ProviderHandler(dependancies.ProviderUsecase(true)))
apiProviderRoutes.GET("", pc.GetProvider)
apiProviderRoutes.PUT("", pc.UpdateProvider)

View file

@ -24,13 +24,13 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareProviderSettingsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
psc := controller.NewProviderSettingsController(dependancies.GetProviderSettingsService())
psc := controller.NewProviderSettingsController(dependancies.ProviderSettingsUsecase())
apiProviderSpecsRoutes := router.Group("/providers/_specs/:psid")
apiProviderSpecsRoutes.Use(middleware.ProviderSpecsHandler)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareProviderSpecsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
pc := controller.NewProviderSpecsController(dependancies.GetProviderSpecsService())
pc := controller.NewProviderSpecsController(dependancies.ProviderSpecsUsecase())
router.GET("/providers/_specs", pc.ListProviders)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareResolverRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
rc := controller.NewResolverController(dependancies.GetResolverService())
rc := controller.NewResolverController(dependancies.ResolverUsecase())
router.POST("/resolver", rc.RunResolver)
}

View file

@ -24,8 +24,7 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
@ -49,7 +48,7 @@ import (
// @name Authorization
// @description Description for what is this security definition being used
func DeclareRoutes(cfg *config.Options, router *gin.Engine, dependancies happydns.UsecaseDependancies) {
func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, dependancies happydns.UsecaseDependancies) {
// Declare routes
baseRoutes := router.Group("")
@ -70,10 +69,10 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine, dependancies happydn
apiAuthRoutes := router.Group("/api")
if cfg.NoAuth {
apiAuthRoutes.Use(middleware.NoAuthMiddleware(dependancies.GetAuthenticationService()))
apiAuthRoutes.Use(middleware.NoAuthMiddleware(dependancies.AuthenticationUsecase()))
} else {
apiAuthRoutes.Use(middleware.JwtAuthMiddleware(dependancies.GetAuthenticationService(), cfg.JWTSigningMethod, cfg.JWTSecretKey))
apiAuthRoutes.Use(middleware.SessionMiddleware(dependancies.GetAuthenticationService()))
apiAuthRoutes.Use(middleware.JwtAuthMiddleware(dependancies.AuthenticationUsecase(), cfg.JWTSigningMethod, cfg.JWTSecretKey))
apiAuthRoutes.Use(middleware.SessionMiddleware(dependancies.AuthenticationUsecase()))
}
apiAuthRoutes.Use(middleware.AuthRequired())

View file

@ -24,20 +24,20 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareZoneServiceRoutes(apiZonesRoutes, apiZonesSubdomainRoutes *gin.RouterGroup, zc *controller.ZoneController, dependancies happydns.UsecaseDependancies) {
sc := controller.NewServiceController(dependancies.GetDomainService(), dependancies.GetServiceService(), dependancies.GetZoneService())
sc := controller.NewServiceController(dependancies.ZoneServiceUsecase(), dependancies.ServiceUsecase(), dependancies.ZoneUsecase())
apiZonesRoutes.PATCH("", sc.UpdateZoneService)
apiZonesSubdomainRoutes.POST("/services", sc.AddZoneService)
apiZonesSubdomainServiceIdRoutes := apiZonesSubdomainRoutes.Group("/services/:serviceid")
apiZonesSubdomainServiceIdRoutes.Use(middleware.ServiceIdHandler(dependancies.GetServiceService()))
apiZonesSubdomainServiceIdRoutes.Use(middleware.ServiceIdHandler(dependancies.ServiceUsecase()))
apiZonesSubdomainServiceIdRoutes.GET("", sc.GetZoneService)
apiZonesSubdomainServiceIdRoutes.DELETE("", sc.DeleteZoneService)

View file

@ -24,13 +24,13 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareServiceSpecsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
ssc := controller.NewServiceSpecsController(dependancies.GetServiceSpecsService())
ssc := controller.NewServiceSpecsController(dependancies.ServiceSpecsUsecase())
router.GET("/service_specs", ssc.ListServiceSpecs)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareSessionRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
sc := controller.NewSessionController(dependancies.GetSessionService())
sc := controller.NewSessionController(dependancies.SessionUsecase())
router.GET("/session", sc.GetSession)
router.DELETE("/session", sc.ClearSession)

View file

@ -24,22 +24,22 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareUsersRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, lc *controller.LoginController) *controller.UserController {
uc := controller.NewUserController(dependancies.GetUserService(), lc)
uc := controller.NewUserController(dependancies.UserUsecase(), lc)
apiUserRoutes := router.Group("/users/:uid")
apiUserRoutes.Use(middleware.UserHandler(dependancies.GetUserService()))
apiUserRoutes.Use(middleware.UserHandler(dependancies.UserUsecase()))
apiUserRoutes.GET("", uc.GetUser)
apiUserRoutes.GET("/avatar.png", uc.GetUserAvatar)
apiSameUserRoutes := router.Group("/users/:uid")
apiSameUserRoutes.Use(middleware.UserHandler(dependancies.GetUserService()))
apiSameUserRoutes.Use(middleware.UserHandler(dependancies.UserUsecase()))
apiSameUserRoutes.Use(middleware.SameUserHandler)
apiSameUserRoutes.DELETE("", uc.DeleteMyUser)

View file

@ -24,18 +24,18 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareUserRecoveryRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, auc *controller.AuthUserController) *controller.UserRecoveryController {
urc := controller.NewUserRecoveryController(dependancies.GetAuthUserService())
urc := controller.NewUserRecoveryController(dependancies.AuthUserUsecase())
router.PATCH("/users", urc.UserRecoveryOperations)
apiUserRoutes := router.Group("/users/:uid")
apiUserRoutes.Use(middleware.AuthUserHandler(dependancies.GetAuthUserService()))
apiUserRoutes.Use(middleware.AuthUserHandler(dependancies.AuthUserUsecase()))
apiUserRoutes.POST("/email", urc.ValidateUserAddress)
apiUserRoutes.POST("/recovery", urc.RecoverUserAccount)

View file

@ -24,12 +24,12 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/model"
)
func DeclareRegistrationRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) *controller.RegistrationController {
rc := controller.NewRegistrationController(dependancies.GetAuthUserService())
rc := controller.NewRegistrationController(dependancies.AuthUserUsecase())
router.POST("/users", rc.RegisterNewUser)

View file

@ -24,7 +24,7 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/api/controller"
)
func DeclareVersionRoutes(router *gin.RouterGroup) {

View file

@ -24,16 +24,20 @@ package route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/api/middleware"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
func DeclareZoneRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
zc := controller.NewZoneController(dependancies.GetZoneService(), dependancies.GetDomainService())
zc := controller.NewZoneController(
dependancies.ZoneUsecase(),
dependancies.DomainUsecase(),
dependancies.ZoneCorrectionApplierUsecase(),
)
apiZonesRoutes := router.Group("/zone/:zoneid")
apiZonesRoutes.Use(middleware.ZoneHandler(dependancies.GetZoneService()))
apiZonesRoutes.Use(middleware.ZoneHandler(dependancies.ZoneUsecase()))
apiZonesRoutes.GET("", zc.GetZone)

View file

@ -32,14 +32,14 @@ import (
"github.com/gin-gonic/gin"
admin "git.happydns.org/happyDomain/api-admin/route"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/usecase"
admin "git.happydns.org/happyDomain/internal/api-admin/route"
providerUC "git.happydns.org/happyDomain/internal/usecase/provider"
"git.happydns.org/happyDomain/model"
)
type Admin struct {
router *gin.Engine
cfg *config.Options
cfg *happydns.Options
srv *http.Server
}
@ -53,7 +53,7 @@ func NewAdmin(app *App) *Admin {
router.Use(gin.Logger(), gin.Recovery())
// Prepare usecases
app.ProviderServiceAdmin = usecase.NewAdminProviderUsecase(app.store)
app.usecases.providerAdmin = providerUC.NewProviderUsecases(app.store)
admin.DeclareRoutes(app.cfg, router, app.store, app)

View file

@ -30,123 +30,183 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
api "git.happydns.org/happyDomain/api/route"
"git.happydns.org/happyDomain/internal/config"
api "git.happydns.org/happyDomain/internal/api/route"
"git.happydns.org/happyDomain/internal/mailer"
"git.happydns.org/happyDomain/internal/newsletter"
"git.happydns.org/happyDomain/internal/session"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/internal/usecase"
authuserUC "git.happydns.org/happyDomain/internal/usecase/authuser"
domainUC "git.happydns.org/happyDomain/internal/usecase/domain"
domainlogUC "git.happydns.org/happyDomain/internal/usecase/domain_log"
"git.happydns.org/happyDomain/internal/usecase/orchestrator"
providerUC "git.happydns.org/happyDomain/internal/usecase/provider"
serviceUC "git.happydns.org/happyDomain/internal/usecase/service"
sessionUC "git.happydns.org/happyDomain/internal/usecase/session"
userUC "git.happydns.org/happyDomain/internal/usecase/user"
zoneUC "git.happydns.org/happyDomain/internal/usecase/zone"
zoneServiceUC "git.happydns.org/happyDomain/internal/usecase/zone_service"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/ui"
"git.happydns.org/happyDomain/usecase"
"git.happydns.org/happyDomain/web"
)
type Usecases struct {
authentication happydns.AuthenticationUsecase
authUser happydns.AuthUserUsecase
domain happydns.DomainUsecase
domainLog happydns.DomainLogUsecase
provider happydns.ProviderUsecase
providerAdmin happydns.ProviderUsecase
providerSpecs happydns.ProviderSpecsUsecase
providerSettings happydns.ProviderSettingsUsecase
resolver happydns.ResolverUsecase
session happydns.SessionUsecase
service happydns.ServiceUsecase
serviceSpecs happydns.ServiceSpecsUsecase
user happydns.UserUsecase
zone happydns.ZoneUsecase
zoneService happydns.ZoneServiceUsecase
orchestrator *orchestrator.Orchestrator
}
type App struct {
cfg *config.Options
mailer *mailer.Mailer
router *gin.Engine
srv *http.Server
insights *insightsCollector
store storage.Storage
AuthenticationService happydns.AuthenticationUsecase
AuthUserService happydns.AuthUserUsecase
DomainService happydns.DomainUsecase
DomainLogService happydns.DomainLogUsecase
ProviderService happydns.ProviderUsecase
ProviderServiceAdmin happydns.ProviderUsecase
ProviderSpecsService happydns.ProviderSpecsUsecase
ProviderSettingsService happydns.ProviderSettingsUsecase
ResolverService happydns.ResolverUsecase
SessionService happydns.SessionUsecase
ServiceService happydns.ServiceUsecase
ServiceSpecsService happydns.ServiceSpecsUsecase
UserService happydns.UserUsecase
ZoneService happydns.ZoneUsecase
cfg *happydns.Options
mailer *mailer.Mailer
newsletter happydns.NewsletterSubscriptor
router *gin.Engine
srv *http.Server
insights *insightsCollector
store storage.Storage
usecases Usecases
}
func (a *App) GetAuthenticationService() happydns.AuthenticationUsecase {
return a.AuthenticationService
func (a *App) AuthenticationUsecase() happydns.AuthenticationUsecase {
return a.usecases.authentication
}
func (a *App) GetAuthUserService() happydns.AuthUserUsecase {
return a.AuthUserService
func (a *App) AuthUserUsecase() happydns.AuthUserUsecase {
return a.usecases.authUser
}
func (a *App) GetDomainService() happydns.DomainUsecase {
return a.DomainService
func (a *App) DomainUsecase() happydns.DomainUsecase {
return a.usecases.domain
}
func (a *App) GetDomainLogService() happydns.DomainLogUsecase {
return a.DomainLogService
func (a *App) DomainLogUsecase() happydns.DomainLogUsecase {
return a.usecases.domainLog
}
func (a *App) GetProviderService(secure bool) happydns.ProviderUsecase {
func (a *App) Orchestrator() *orchestrator.Orchestrator {
return a.usecases.orchestrator
}
func (a *App) ProviderUsecase(secure bool) happydns.ProviderUsecase {
if secure {
return a.ProviderService
return a.usecases.provider
} else {
return a.ProviderServiceAdmin
return a.usecases.providerAdmin
}
}
func (a *App) GetProviderSettingsService() happydns.ProviderSettingsUsecase {
return a.ProviderSettingsService
func (a *App) ProviderSettingsUsecase() happydns.ProviderSettingsUsecase {
return a.usecases.providerSettings
}
func (a *App) GetProviderSpecsService() happydns.ProviderSpecsUsecase {
return a.ProviderSpecsService
func (a *App) ProviderSpecsUsecase() happydns.ProviderSpecsUsecase {
return a.usecases.providerSpecs
}
func (a *App) GetResolverService() happydns.ResolverUsecase {
return a.ResolverService
func (a *App) ResolverUsecase() happydns.ResolverUsecase {
return a.usecases.resolver
}
func (a *App) GetServiceService() happydns.ServiceUsecase {
return a.ServiceService
func (a *App) RemoteZoneImporterUsecase() happydns.RemoteZoneImporterUsecase {
return a.usecases.orchestrator.RemoteZoneImporter
}
func (a *App) GetServiceSpecsService() happydns.ServiceSpecsUsecase {
return a.ServiceSpecsService
func (a *App) ServiceUsecase() happydns.ServiceUsecase {
return a.usecases.service
}
func (a *App) GetSessionService() happydns.SessionUsecase {
return a.SessionService
func (a *App) ServiceSpecsUsecase() happydns.ServiceSpecsUsecase {
return a.usecases.serviceSpecs
}
func (a *App) GetUserService() happydns.UserUsecase {
return a.UserService
func (a *App) SessionUsecase() happydns.SessionUsecase {
return a.usecases.session
}
func (a *App) GetZoneService() happydns.ZoneUsecase {
return a.ZoneService
func (a *App) UserUsecase() happydns.UserUsecase {
return a.usecases.user
}
func NewApp(cfg *config.Options) *App {
func (a *App) ZoneCorrectionApplierUsecase() happydns.ZoneCorrectionApplierUsecase {
return a.usecases.orchestrator.ZoneCorrectionApplier
}
func (a *App) ZoneImporterUsecase() happydns.ZoneImporterUsecase {
return a.usecases.orchestrator.ZoneImporter
}
func (a *App) ZoneUsecase() happydns.ZoneUsecase {
return a.usecases.zone
}
func (a *App) ZoneServiceUsecase() happydns.ZoneServiceUsecase {
return a.usecases.zoneService
}
func NewApp(cfg *happydns.Options) *App {
app := &App{
cfg: cfg,
}
// Initialize mailer
if cfg.MailSMTPHost != "" {
app.initMailer()
app.initStorageEngine()
app.initNewsletter()
app.initInsights()
app.initUsecases()
app.setupRouter()
return app
}
func NewAppWithStorage(cfg *happydns.Options, store storage.Storage) *App {
app := &App{
cfg: cfg,
store: store,
}
app.initMailer()
app.initNewsletter()
app.initUsecases()
app.setupRouter()
return app
}
func (app *App) initMailer() {
if app.cfg.MailSMTPHost != "" {
app.mailer = &mailer.Mailer{
MailFrom: &cfg.MailFrom,
SendMethod: mailer.NewSMTPMailer(cfg.MailSMTPHost, cfg.MailSMTPPort, cfg.MailSMTPUsername, cfg.MailSMTPPassword),
MailFrom: &app.cfg.MailFrom,
SendMethod: mailer.NewSMTPMailer(app.cfg.MailSMTPHost, app.cfg.MailSMTPPort, app.cfg.MailSMTPUsername, app.cfg.MailSMTPPassword),
}
if cfg.MailSMTPTLSSNoVerify {
if app.cfg.MailSMTPTLSSNoVerify {
app.mailer.SendMethod.(*mailer.SMTPMailer).WithTLSNoVerify()
}
} else if !cfg.NoMail {
} else if !app.cfg.NoMail {
app.mailer = &mailer.Mailer{
MailFrom: &cfg.MailFrom,
MailFrom: &app.cfg.MailFrom,
SendMethod: &mailer.SystemSendmail{},
}
}
}
// Initialize storage
if s, ok := storage.StorageEngines[cfg.StorageEngine]; !ok {
log.Fatalf("Nonexistent storage engine: %q, please select one of: %v", cfg.StorageEngine, storage.GetStorageEngines())
func (app *App) initStorageEngine() {
if s, ok := storage.StorageEngines[app.cfg.StorageEngine]; !ok {
log.Fatalf("Nonexistent storage engine: %q, please select one of: %v", app.cfg.StorageEngine, storage.GetStorageEngines())
} else {
var err error
log.Println("Opening database...")
@ -156,48 +216,73 @@ func NewApp(cfg *config.Options) *App {
}
log.Println("Performing database migrations...")
if err = app.store.DoMigration(); err != nil {
if err = app.store.MigrateSchema(); err != nil {
log.Fatal("Could not migrate database: ", err)
}
}
}
// Initialize newsletter registration
var ns happydns.NewsletterSubscriptor
if cfg.ListmonkURL.URL != nil {
ns = &newsletter.ListmonkNewsletterSubscription{
ListmonkURL: cfg.ListmonkURL.URL,
ListmonkId: cfg.ListmonkId,
func (app *App) initNewsletter() {
if app.cfg.ListmonkURL.String() != "" {
app.newsletter = &newsletter.ListmonkNewsletterSubscription{
ListmonkURL: &app.cfg.ListmonkURL,
ListmonkId: app.cfg.ListmonkId,
}
} else {
ns = &newsletter.DummyNewsletterSubscription{}
app.newsletter = &newsletter.DummyNewsletterSubscription{}
}
}
if !cfg.OptOutInsights {
func (app *App) initInsights() {
if !app.cfg.OptOutInsights {
app.insights = &insightsCollector{
cfg: app.cfg,
store: app.store,
stop: make(chan bool),
}
}
}
// Prepare usecases
app.ProviderSpecsService = usecase.NewProviderSpecsUsecase()
app.ProviderSettingsService = usecase.NewProviderSettingsUsecase(cfg, app.store)
app.ProviderService = usecase.NewProviderUsecase(cfg, app.store)
app.ServiceService = usecase.NewServiceUsecase()
app.ServiceSpecsService = usecase.NewServiceSpecsUsecase()
app.ZoneService = usecase.NewZoneUsecase(app.ProviderService, app.ServiceService, app.store)
app.DomainLogService = usecase.NewDomainLogUsecase(app.store)
app.DomainService = usecase.NewDomainUsecase(app.store, app.DomainLogService, app.ProviderService, app.ZoneService)
func (app *App) initUsecases() {
sessionService := sessionUC.NewSessionUsecases(app.store)
authUserService := authuserUC.NewAuthUserUsecases(app.cfg, app.mailer, app.store, sessionService.CloseUserSessionsUC)
domainLogService := domainlogUC.NewDomainLogUsecases(app.store)
providerService := providerUC.NewRestrictedProviderUsecases(app.cfg, app.store)
serviceService := serviceUC.NewServiceUsecases()
zoneService := zoneUC.NewZoneUsecases(app.store)
app.UserService = usecase.NewUserUsecase(app.store, ns)
app.AuthenticationService = usecase.NewAuthenticationUsecase(cfg, app.store, app.UserService)
app.AuthUserService = usecase.NewAuthUserUsecase(cfg, app.mailer, app.store)
app.ResolverService = usecase.NewResolverUsecase(cfg)
app.SessionService = usecase.NewSessionUsecase(app.store)
app.usecases.providerSpecs = usecase.NewProviderSpecsUsecase()
app.usecases.provider = providerService
app.usecases.providerSettings = usecase.NewProviderSettingsUsecase(app.cfg, app.usecases.provider, app.store)
app.usecases.service = serviceService
app.usecases.serviceSpecs = usecase.NewServiceSpecsUsecase()
app.usecases.zone = zoneService
app.usecases.domainLog = domainLogService
// Initialize router
if cfg.DevProxy == "" {
domainService := domainUC.NewDomainUsecases(app.store, providerService.GetProviderUC, zoneService.GetZoneUC, providerService.DomainExistenceUC, domainLogService.CreateDomainLogUC)
app.usecases.domain = domainService
app.usecases.zoneService = zoneServiceUC.NewZoneServiceUsecases(domainService.UpdateDomainUC, zoneService.CreateZoneUC, serviceService.ValidateServiceUC, app.store)
app.usecases.user = userUC.NewUserUsecases(app.store, app.newsletter, authUserService.GetAuthUserUC, sessionService.CloseUserSessionsUC)
app.usecases.authentication = usecase.NewAuthenticationUsecase(app.cfg, app.store, app.usecases.user)
app.usecases.authUser = authUserService
app.usecases.resolver = usecase.NewResolverUsecase(app.cfg)
app.usecases.session = sessionService
app.usecases.orchestrator = orchestrator.NewOrchestrator(
domainLogService.CreateDomainLogUC,
domainService.UpdateDomainUC,
providerService.GetProviderUC,
zoneService.ListRecordsUC,
providerService.ZoneCorrectionsUC,
zoneService.CreateZoneUC,
providerService.RetrieveZoneUC,
zoneService.UpdateZoneUC,
)
}
func (app *App) setupRouter() {
if app.cfg.DevProxy == "" {
gin.SetMode(gin.ReleaseMode)
}
@ -205,13 +290,11 @@ func NewApp(cfg *config.Options) *App {
app.router = gin.New()
app.router.Use(gin.Logger(), gin.Recovery(), sessions.Sessions(
session.COOKIE_NAME,
session.NewSessionStore(cfg, app.store, []byte(cfg.JWTSecretKey)),
session.NewSessionStore(app.cfg, app.store, []byte(app.cfg.JWTSecretKey)),
))
api.DeclareRoutes(cfg, app.router, app)
ui.DeclareRoutes(cfg, app.router)
return app
api.DeclareRoutes(app.cfg, app.router, app)
web.DeclareRoutes(app.cfg, app.router)
}
func (app *App) Start() {

View file

@ -33,8 +33,7 @@ import (
"strings"
"time"
"git.happydns.org/happyDomain/api/controller"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
@ -45,7 +44,7 @@ const (
)
type insightsCollector struct {
cfg *config.Options
cfg *happydns.Options
store storage.Storage
stop chan bool
}
@ -117,11 +116,12 @@ func (c *insightsCollector) collect() (*happydns.Insights, error) {
data.Config.DisableEmbeddedLogin = c.cfg.DisableEmbeddedLogin
data.Config.DisableProviders = c.cfg.DisableProviders
data.Config.DisableRegistration = c.cfg.DisableRegistration
data.Config.HasBaseURL = c.cfg.GetBasePath() != ""
data.Config.HasBaseURL = c.cfg.BasePath != ""
data.Config.HasDevProxy = c.cfg.DevProxy != ""
data.Config.HasExternalAuth = c.cfg.ExternalAuth.String() != ""
data.Config.HasListmonkURL = c.cfg.ListmonkURL.String() != ""
data.Config.LocalBind = strings.HasPrefix(c.cfg.Bind, "127.0.0.1:") || strings.HasPrefix(c.cfg.Bind, "[::1]:")
data.Config.NbOidcProviders = len(c.cfg.OIDCClients)
data.Config.NoAuthActive = c.cfg.NoAuth
data.Config.NoMail = c.cfg.NoMail
data.Config.NonUnixAdminBind = strings.Contains(c.cfg.AdminBind, ":")
@ -130,28 +130,32 @@ func (c *insightsCollector) collect() (*happydns.Insights, error) {
// Database info
data.Database.Version = c.store.SchemaVersion()
if authusers, err := c.store.GetAuthUsers(); err != nil {
if authusers, err := c.store.ListAllAuthUsers(); err != nil {
return nil, err
} else {
data.Database.NbAuthUsers = len(authusers)
for authusers.Next() {
data.Database.NbAuthUsers++
}
}
users, err := c.store.GetUsers()
users, err := c.store.ListAllUsers()
if err != nil {
return nil, err
} else {
data.Database.NbUsers = len(users)
}
data.Database.Providers = map[string]int{}
for _, user := range users {
for users.Next() {
data.Database.NbUsers++
user := users.Item()
if providers, err := c.store.ListProviders(user); err == nil {
for _, provider := range providers {
data.Database.Providers[provider.Type] += 1
}
}
if domains, err := c.store.GetDomains(user); err == nil {
if domains, err := c.store.ListDomains(user); err == nil {
data.Database.NbDomains += len(domains)
for _, domain := range domains {

View file

@ -26,26 +26,27 @@ import (
"fmt"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
// declareFlags registers flags for the structure Options.
func (o *Options) declareFlags() {
func declareFlags(o *happydns.Options) {
flag.StringVar(&o.DevProxy, "dev", o.DevProxy, "Proxify traffic to this host for static assets")
flag.StringVar(&o.AdminBind, "admin-bind", o.AdminBind, "Bind port/socket for administration interface")
flag.StringVar(&o.Bind, "bind", ":8081", "Bind port/socket")
flag.BoolVar(&o.DisableProviders, "disable-providers-edit", o.DisableProviders, "Disallow all actions on provider (add/edit/delete)")
flag.BoolVar(&o.DisableRegistration, "disable-registration", o.DisableRegistration, "Forbids new account creation through public form/API (still allow registration from external services)")
flag.BoolVar(&o.DisableEmbeddedLogin, "disable-embedded-login", o.DisableEmbeddedLogin, "Disables the internal user/password login in favor of external-auth or OIDC")
flag.Var(&o.ExternalURL, "externalurl", "Begining of the URL, before the base, that should be used eg. in mails")
flag.StringVar(&o.baseURL, "baseurl", o.baseURL, "URL prepended to each URL")
flag.Var(&URL{&o.ExternalURL}, "externalurl", "Begining of the URL, before the base, that should be used eg. in mails")
flag.StringVar(&o.BasePath, "baseurl", o.BasePath, "URL prepended to each URL")
flag.StringVar(&o.DefaultNameServer, "default-ns", o.DefaultNameServer, "Adress to the default name server")
flag.Var(&o.StorageEngine, "storage-engine", fmt.Sprintf("Select the storage engine between %v", storage.GetStorageEngines()))
flag.StringVar(&o.StorageEngine, "storage-engine", o.StorageEngine, fmt.Sprintf("Select the storage engine between %v", storage.GetStorageEngines()))
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
flag.Var(&o.JWTSecretKey, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
flag.Var(&o.ExternalAuth, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
flag.Var(&JWTSecretKey{&o.JWTSecretKey}, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
flag.Var(&URL{&o.ExternalAuth}, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
flag.BoolVar(&o.OptOutInsights, "opt-out-insights", false, "Disable the anonymous usage statistics report. If you care about this project and don't participate in discussions, don't opt-out.")
flag.Var(&o.ListmonkURL, "newsletter-server-url", "Base URL of the listmonk newsletter server")
flag.Var(&URL{&o.ListmonkURL}, "newsletter-server-url", "Base URL of the listmonk newsletter server")
flag.IntVar(&o.ListmonkId, "newsletter-id", 1, "Listmonk identifier of the list receiving the new user")
flag.BoolVar(&o.NoMail, "no-mail", o.NoMail, "Disable all automatic mails, skip email verification at registration")
@ -60,11 +61,11 @@ func (o *Options) declareFlags() {
}
// parseCLI parse the flags and treats extra args as configuration filename.
func (o *Options) parseCLI() error {
func parseCLI(o *happydns.Options) error {
flag.Parse()
for _, conf := range flag.Args() {
err := o.parseFile(conf)
err := parseFile(o, conf)
if err != nil {
return err
}

View file

@ -32,104 +32,30 @@ import (
"path"
"strings"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
// Options stores the configuration of the software.
type Options struct {
// AdminBind is the address:port or unix socket used to serve the admin
// API.
AdminBind string
// Bind is the address:port used to bind the main interface with API.
Bind string
// BaseURL is the relative path where begins the root of the app.
baseURL string
// DevProxy is the URL that override static assets.
DevProxy string
// DefaultNameServer is the NS server suggested by default.
DefaultNameServer string
// DisableProviders should disallow all actions on provider (add/edit/delete) through public API.
DisableProviders bool
// DisableRegistration forbids all new registration using the public form/API.
DisableRegistration bool
// DisableEmbeddedLogin disables the internal user/password login in favor of ExternalAuth or OIDC.
DisableEmbeddedLogin bool
// ExternalAuth is the URL of the login form to use instead of the embedded one.
ExternalAuth URL
// ExternalURL keeps the URL used in communications (such as email,
// ...), when it needs to use complete URL, not only relative parts.
ExternalURL URL
// JWTSecretKey stores the private key to sign and verify JWT tokens.
JWTSecretKey JWTSecretKey
// JWTSigningMethod is the signing method to check token signature.
JWTSigningMethod string
// NoAuth controls if there is user access control or not.
NoAuth bool
// OptOutInsights disable the anonymous usage statistics report.
OptOutInsights bool
// StorageEngine points to the storage engine used.
StorageEngine storage.StorageEngine
ListmonkURL URL
ListmonkId int
// MailFrom holds the content of the From field for all e-mails that
// will be send.
MailFrom mail.Address
NoMail bool
MailSMTPHost string
MailSMTPPort uint
MailSMTPUsername string
MailSMTPPassword string
MailSMTPTLSSNoVerify bool
}
// GetBaseURL returns the full url to the absolute ExternalURL, including BaseURL.
func (o *Options) GetBaseURL() string {
return fmt.Sprintf("%s%s", o.ExternalURL.URL.String(), o.baseURL)
}
// GetBasePath returns the baseURL.
func (o *Options) GetBasePath() string {
return o.baseURL
}
// ConsolidateConfig fills an Options struct by reading configuration from
// config files, environment, then command line.
//
// Should be called only one time.
func ConsolidateConfig() (opts *Options, err error) {
func ConsolidateConfig() (opts *happydns.Options, err error) {
u, _ := url.Parse("http://localhost:8081")
// Define defaults options
opts = &Options{
opts = &happydns.Options{
AdminBind: "./happydomain.sock",
baseURL: "/",
BasePath: "/",
Bind: ":8081",
DefaultNameServer: "127.0.0.1:53",
ExternalURL: URL{URL: u},
ExternalURL: *u,
JWTSigningMethod: "HS512",
MailFrom: mail.Address{Name: "happyDomain", Address: "happydomain@localhost"},
MailSMTPPort: 587,
StorageEngine: storage.StorageEngine("leveldb"),
StorageEngine: "leveldb",
}
opts.declareFlags()
declareFlags(opts)
// Establish a list of possible configuration file locations
configLocations := []string{
@ -146,7 +72,7 @@ func ConsolidateConfig() (opts *Options, err error) {
for _, filename := range configLocations {
if _, e := os.Stat(filename); !os.IsNotExist(e) {
log.Printf("Loading configuration from %s\n", filename)
err = opts.parseFile(filename)
err = parseFile(opts, filename)
if err != nil {
return
}
@ -155,22 +81,22 @@ func ConsolidateConfig() (opts *Options, err error) {
}
// Then, overwrite that by what is present in the environment
err = opts.parseEnvironmentVariables()
err = parseEnvironmentVariables(opts)
if err != nil {
return
}
// Finaly, command line takes precedence
err = opts.parseCLI()
err = parseCLI(opts)
if err != nil {
return
}
// Sanitize options
if opts.baseURL != "/" {
opts.baseURL = path.Clean(opts.baseURL)
if opts.BasePath != "/" {
opts.BasePath = path.Clean(opts.BasePath)
} else {
opts.baseURL = ""
opts.BasePath = ""
}
if opts.NoMail && opts.MailSMTPHost != "" {
@ -178,26 +104,26 @@ func ConsolidateConfig() (opts *Options, err error) {
return
}
if opts.ExternalURL.URL.Host == "" || opts.ExternalURL.URL.Scheme == "" {
u, err2 := url.Parse("http://" + opts.ExternalURL.URL.String())
if opts.ExternalURL.Host == "" || opts.ExternalURL.Scheme == "" {
u, err2 := url.Parse("http://" + opts.ExternalURL.String())
if err2 == nil {
opts.ExternalURL.URL = u
opts.ExternalURL = *u
} else {
err = fmt.Errorf("You defined an external URL without a scheme. The expected value is eg. http://localhost:8081")
return
}
}
if len(opts.ExternalURL.URL.Path) > 1 {
if opts.baseURL != "" && opts.baseURL != opts.ExternalURL.URL.Path {
if len(opts.ExternalURL.Path) > 1 {
if opts.BasePath != "" && opts.BasePath != opts.ExternalURL.Path {
err = fmt.Errorf("You defined both baseurl and a path to externalurl that are different. Define only one of those.")
return
}
opts.baseURL = path.Clean(opts.ExternalURL.URL.Path)
opts.BasePath = path.Clean(opts.ExternalURL.Path)
}
opts.ExternalURL.URL.Path = ""
opts.ExternalURL.URL.Fragment = ""
opts.ExternalURL.URL.RawQuery = ""
opts.ExternalURL.Path = ""
opts.ExternalURL.Fragment = ""
opts.ExternalURL.RawQuery = ""
if len(opts.JWTSecretKey) == 0 {
opts.JWTSecretKey = make([]byte, 32)
@ -207,12 +133,17 @@ func ConsolidateConfig() (opts *Options, err error) {
}
}
err = ExtendsConfigWithOIDC(opts)
if err != nil {
return
}
return
}
// parseLine treats a config line and place the read value in the variable
// declared to the corresponding flag.
func (o *Options) parseLine(line string) (err error) {
func parseLine(o *happydns.Options, line string) (err error) {
fields := strings.SplitN(line, "=", 2)
orig_key := strings.TrimSpace(fields[0])
value := strings.TrimSpace(fields[1])

View file

@ -19,18 +19,20 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config // import "git.happydns.org/happyDomain/config"
package config // import "git.happydns.org/happyDomain/internal/config"
import (
"net/url"
"testing"
"git.happydns.org/happyDomain/model"
)
func TestParseLine(t *testing.T) {
cfg := Options{}
cfg.declareFlags()
cfg := &happydns.Options{}
declareFlags(cfg)
err := cfg.parseLine("HAPPYDOMAIN_BIND=:8080")
err := parseLine(cfg, "HAPPYDOMAIN_BIND=:8080")
if err != nil {
t.Fatalf(`parseLine("BIND=:8080") => %v`, err.Error())
}
@ -38,30 +40,30 @@ func TestParseLine(t *testing.T) {
t.Fatalf(`parseLine("BIND=:8080") = %q, want ":8080"`, cfg.Bind)
}
err = cfg.parseLine("BASEURL=/base")
err = parseLine(cfg, "BASEURL=/base")
if err != nil {
t.Fatalf(`parseLine("BASEURL=/base") => %v`, err.Error())
}
if cfg.baseURL != "/base" {
t.Fatalf(`parseLine("BASEURL=/base") = %q, want "/base"`, cfg.baseURL)
if cfg.BasePath != "/base" {
t.Fatalf(`parseLine("BASEURL=/base") = %q, want "/base"`, cfg.BasePath)
}
cfg.parseLine("EXTERNALURL=https://happydomain.org")
parseLine(cfg, "EXTERNALURL=https://happydomain.org")
if cfg.ExternalURL.String() != "https://happydomain.org" {
t.Fatalf(`parseLine("EXTERNAL_URL=https://happydomain.org") = %q, want "https://happydomain.org"`, cfg.ExternalURL)
t.Fatalf(`parseLine("EXTERNAL_URL=https://happydomain.org") = %q, want "https://happydomain.org"`, cfg.ExternalURL.String())
}
cfg.parseLine("DEFAULT-NS=42.42.42.42:5353")
parseLine(cfg, "DEFAULT-NS=42.42.42.42:5353")
if cfg.DefaultNameServer != "42.42.42.42:5353" {
t.Fatalf(`parseLine("DEFAULT-NS=42.42.42.42:5353") = %q, want "42.42.42.42:5353"`, cfg.DefaultNameServer)
}
cfg.parseLine("DEFAULT_NS=42.42.42.42:3535")
parseLine(cfg, "DEFAULT_NS=42.42.42.42:3535")
if cfg.DefaultNameServer != "42.42.42.42:3535" {
t.Fatalf(`parseLine("DEFAULT_NS=42.42.42.42:3535") = %q, want "42.42.42.42:3535"`, cfg.DefaultNameServer)
}
err = cfg.parseLine("NO_AUTH=true")
err = parseLine(cfg, "NO_AUTH=true")
if err != nil {
t.Fatalf(`parseLine("NO_AUTH=true") => %v`, err.Error())
}
@ -73,8 +75,8 @@ func TestParseLine(t *testing.T) {
func TestGetBaseURL(t *testing.T) {
u, _ := url.Parse("http://localhost:8081")
cfg := Options{
ExternalURL: URL{URL: u},
cfg := &happydns.Options{
ExternalURL: *u,
}
builded_url := cfg.GetBaseURL()
@ -82,7 +84,7 @@ func TestGetBaseURL(t *testing.T) {
t.Fatalf(`GetBaseURL() = %q, want "http://localhost:8081"`, builded_url)
}
cfg.baseURL = "/base"
cfg.BasePath = "/base"
builded_url = cfg.GetBaseURL()
if builded_url != "http://localhost:8081/base" {

View file

@ -27,10 +27,12 @@ import (
"net/url"
)
type JWTSecretKey []byte
type JWTSecretKey struct {
Secret *[]byte
}
func (i *JWTSecretKey) String() string {
return base64.StdEncoding.EncodeToString(*i)
return base64.StdEncoding.EncodeToString(*i.Secret)
}
func (i *JWTSecretKey) Set(value string) error {
@ -39,7 +41,7 @@ func (i *JWTSecretKey) Set(value string) error {
return err
}
*i = z
*i.Secret = z
return nil
}
@ -83,6 +85,6 @@ func (i *URL) Set(value string) error {
return err
}
i.URL = u
*i.URL = *u
return nil
}

View file

@ -25,14 +25,16 @@ import (
"fmt"
"os"
"strings"
"git.happydns.org/happyDomain/model"
)
// parseEnvironmentVariables analyzes all the environment variables to find
// each one starting by HAPPYDOMAIN_
func (o *Options) parseEnvironmentVariables() (err error) {
func parseEnvironmentVariables(o *happydns.Options) (err error) {
for _, line := range os.Environ() {
if strings.HasPrefix(line, "HAPPYDOMAIN_") || strings.HasPrefix(line, "HAPPYDNS_") {
err := o.parseLine(line)
err := parseLine(o, line)
if err != nil {
return fmt.Errorf("error in environment (%q): %w", line, err)
}

View file

@ -26,11 +26,13 @@ import (
"fmt"
"os"
"strings"
"git.happydns.org/happyDomain/model"
)
// parseFile opens the file at the given filename path, then treat each line
// not starting with '#' as a configuration statement.
func (o *Options) parseFile(filename string) error {
func parseFile(o *happydns.Options, filename string) error {
fp, err := os.Open(filename)
if err != nil {
return err
@ -43,7 +45,7 @@ func (o *Options) parseFile(filename string) error {
n += 1
line := strings.TrimSpace(scanner.Text())
if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
err := o.parseLine(line)
err := parseLine(o, line)
if err != nil {
return fmt.Errorf("%v:%d: error in configuration: %w", filename, n, err)
}

View file

@ -23,6 +23,10 @@
package config
func (o *Options) GetOIDCProviderURL() string {
return ""
import (
"git.happydns.org/happyDomain/model"
)
func ExtendsConfigWithOIDC(o *happydns.Options) error {
return nil
}

View file

@ -24,50 +24,32 @@
package config
import (
"context"
"flag"
"net/url"
"path"
"strings"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
"git.happydns.org/happyDomain/model"
)
var (
OIDCClientID string
oidcClientID string
oidcClientSecret string
OIDCProviderURL string
oidcProviderURL url.URL
)
func init() {
flag.StringVar(&OIDCClientID, "oidc-client-id", OIDCClientID, "ClientID for OIDC")
flag.StringVar(&oidcClientID, "oidc-client-id", oidcClientID, "ClientID for OIDC")
flag.StringVar(&oidcClientSecret, "oidc-client-secret", oidcClientSecret, "Secret for OIDC")
flag.StringVar(&OIDCProviderURL, "oidc-provider-url", OIDCProviderURL, "Base URL of the OpenId Connect service")
flag.Var(&URL{&oidcProviderURL}, "oidc-provider-url", "Base URL of the OpenId Connect service")
}
func (o *Options) GetAuthURL() *url.URL {
redirecturl := *o.ExternalURL.URL
redirecturl.Path = path.Join(redirecturl.Path, o.baseURL, "auth", "callback")
return &redirecturl
}
func (o *Options) GetOIDCProvider(ctx context.Context) (*oidc.Provider, error) {
return oidc.NewProvider(ctx, strings.TrimSuffix(OIDCProviderURL, "/.well-known/openid-configuration"))
}
func (o *Options) GetOIDCProviderURL() string {
return OIDCProviderURL
}
func (o *Options) GetOAuth2Config(provider *oidc.Provider) *oauth2.Config {
oauth2Config := oauth2.Config{
ClientID: OIDCClientID,
ClientSecret: oidcClientSecret,
RedirectURL: o.GetAuthURL().String(),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
func ExtendsConfigWithOIDC(o *happydns.Options) error {
if oidcProviderURL.String() != "" {
o.OIDCClients = append(o.OIDCClients, happydns.OIDCSettings{
ClientID: oidcClientID,
ClientSecret: oidcClientSecret,
ProviderURL: oidcProviderURL,
})
}
return &oauth2Config
return nil
}

View file

@ -0,0 +1,82 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !nooidc
package config // import "git.happydns.org/happyDomain/internal/config"
import (
"testing"
"git.happydns.org/happyDomain/model"
)
func TestOIDCConfig(t *testing.T) {
cfg := &happydns.Options{}
err := parseLine(cfg, "HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1")
if err != nil {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1") => %v`, err.Error())
}
if oidcClientID != "test-oidc-1" {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1") = %q, want "test-oidc-1"`, oidcClientID)
}
err = parseLine(cfg, "HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$")
if err != nil {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$") => %v`, err.Error())
}
if oidcClientSecret != "s3cret$" {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$") = %q, want "s3cret$"`, oidcClientSecret)
}
if oidcProviderURL.String() != "" {
t.Fatalf(`before parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL") = %q, want ""`, oidcProviderURL.String())
}
err = parseLine(cfg, "HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret")
if err != nil {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret") => %v`, err.Error())
}
if oidcProviderURL.String() != "https://localhost:12345/secret" {
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret") = %q, want "https://localhost:12345/secret"`, cfg.Bind)
}
// Test extended config
err = ExtendsConfigWithOIDC(cfg)
if err != nil {
t.Fatalf(`ExtendsConfigWithOIDC(cfg) => %v`, err.Error())
}
if len(cfg.OIDCClients) != 1 {
t.Fatalf(`len(cfg.OIDCClients) == %d, should be 1`, len(cfg.OIDCClients))
}
if cfg.OIDCClients[0].ClientID != "test-oidc-1" {
t.Fatalf(`cfg.OIDCClients[0].ClientID == %q, should be test-oidc-1`, cfg.OIDCClients[0].ClientID)
}
if cfg.OIDCClients[0].ClientSecret != "s3cret$" {
t.Fatalf(`cfg.OIDCClients[0].ClientSecret == %q, should be test-oidc-1`, cfg.OIDCClients[0].ClientSecret)
}
if cfg.OIDCClients[0].ProviderURL.String() != "https://localhost:12345/secret" {
t.Fatalf(`cfg.OIDCClients[0].ProviderURL == %q, should be https://localhost:12345/secret`, cfg.OIDCClients[0].ProviderURL.String())
}
}

View file

@ -19,7 +19,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package utils
package helpers
import (
"strings"

View file

@ -19,7 +19,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package utils
package helpers
import (
"github.com/miekg/dns"

View file

@ -19,7 +19,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package utils
package helpers
import (
"crypto/rand"

View file

@ -19,7 +19,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package utils
package helpers
import (
"strings"

View file

@ -27,7 +27,7 @@ import (
"net/mail"
"text/template"
"git.happydns.org/happyDomain/ui"
"git.happydns.org/happyDomain/web"
gomail "github.com/go-mail/mail"
"github.com/yuin/goldmark"
@ -95,7 +95,7 @@ func (r *Mailer) SendMail(to *mail.Address, subject, content string) (err error)
return
}
if data, err := ui.GetEmbedFS().Open("dist/img/happydomain.png"); err == nil {
if data, err := web.GetEmbedFS().Open("dist/img/happydomain.png"); err == nil {
m.EmbedReader("happydomain.png", data)
}

View file

@ -30,7 +30,7 @@ import (
"net/url"
"path/filepath"
"git.happydns.org/happyDomain/internal/utils"
"git.happydns.org/happyDomain/internal/helpers"
"git.happydns.org/happyDomain/model"
)
@ -49,8 +49,8 @@ type ListmonkSubscriber struct {
}
func (ns *ListmonkNewsletterSubscription) SubscribeToNewsletter(u happydns.UserInfo) error {
if ns.ListmonkId != 0 {
log.Println("SubscribeToNewsletter: not subscribing user as newsletter server is not defined.")
if ns.ListmonkId == 0 {
log.Println("SubscribeToNewsletter: not subscribing user as newsletter list id is not defined.")
return nil
}
@ -59,7 +59,7 @@ func (ns *ListmonkNewsletterSubscription) SubscribeToNewsletter(u happydns.UserI
jsonForm := &ListmonkSubscriber{
Email: u.GetEmail(),
Name: utils.GenUsername(u.GetEmail()),
Name: helpers.GenUsername(u.GetEmail()),
Status: "enabled",
Lists: []int{ns.ListmonkId},
PreconfirmSubscriptions: true,

View file

@ -22,7 +22,6 @@
package session // import "git.happydns.org/happyDomain/internal/session"
import (
"encoding/base32"
"fmt"
"net/http"
"strings"
@ -33,8 +32,7 @@ import (
"github.com/gorilla/sessions"
"github.com/mileusna/useragent"
"git.happydns.org/happyDomain/internal/config"
"git.happydns.org/happyDomain/internal/storage"
sessionUC "git.happydns.org/happyDomain/internal/usecase/session"
"git.happydns.org/happyDomain/model"
)
@ -44,18 +42,18 @@ const COOKIE_NAME = "happydomain_session"
type SessionStore struct {
Codecs []securecookie.Codec
options *sessions.Options
storage storage.Storage
storage sessionUC.SessionStorage
}
func NewSessionStore(opts *config.Options, storage storage.Storage, keyPairs ...[]byte) *SessionStore {
func NewSessionStore(opts *happydns.Options, storage sessionUC.SessionStorage, keyPairs ...[]byte) *SessionStore {
store := &SessionStore{
Codecs: securecookie.CodecsFromPairs(keyPairs...),
options: &sessions.Options{
Path: opts.GetBasePath() + "/",
Path: opts.BasePath + "/",
MaxAge: 86400 * 30,
Secure: opts.DevProxy == "" && opts.ExternalURL.URL.Scheme != "http",
Secure: opts.DevProxy == "" && opts.ExternalURL.Scheme != "http",
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
SameSite: http.SameSiteLaxMode,
},
storage: storage,
}
@ -107,7 +105,7 @@ func (s *SessionStore) Save(r *http.Request, w http.ResponseWriter, session *ses
s.storage.DeleteSession(session.ID)
} else {
if session.ID == "" {
session.ID = NewSessionId()
session.ID = sessionUC.NewSessionId()
}
encrypted, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
if err != nil {
@ -173,13 +171,28 @@ func (s *SessionStore) load(session *sessions.Session) error {
// save writes encoded session.Values to a database record.
func (s *SessionStore) save(session *sessions.Session, ua string) error {
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...)
if err != nil {
return err
var iduser happydns.Identifier
if iu, ok := session.Values["iduser"].(happydns.Identifier); ok {
iduser = iu
} else if iu, ok := session.Values["iduser"].([]byte); ok {
iduser = happydns.Identifier(iu)
}
delete(session.Values, "iduser")
var description string
if descr, ok := session.Values["description"].(string); ok {
description = descr
} else {
browser := useragent.Parse(ua)
description = fmt.Sprintf("%s on %s", browser.Name, browser.OS)
session.Values["description"] = description
}
delete(session.Values, "description")
crOn := session.Values["created_on"]
delete(session.Values, "created_on")
exOn := session.Values["expires_on"]
delete(session.Values, "expires_on")
var expiresOn time.Time
@ -197,20 +210,9 @@ func (s *SessionStore) save(session *sessions.Session, ua string) error {
}
}
var iduser happydns.Identifier
if iu, ok := session.Values["iduser"].(happydns.Identifier); ok {
iduser = iu
} else if iu, ok := session.Values["iduser"].([]byte); ok {
iduser = happydns.Identifier(iu)
}
var description string
if descr, ok := session.Values["description"].(string); ok {
description = descr
} else {
browser := useragent.Parse(ua)
description = fmt.Sprintf("%s on %s", browser.Name, browser.OS)
session.Values["description"] = description
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...)
if err != nil {
return err
}
mysession := &happydns.Session{
@ -225,7 +227,3 @@ func (s *SessionStore) save(session *sessions.Session, ua string) error {
return s.storage.UpdateSession(mysession)
}
func NewSessionId() string {
return strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(64)), "=")
}

View file

@ -0,0 +1,129 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package inmemory
import (
"git.happydns.org/happyDomain/model"
)
func (s *InMemoryStorage) ListAllAuthUsers() (happydns.Iterator[happydns.UserAuth], error) {
s.mu.Lock()
defer s.mu.Unlock()
return NewInMemoryIterator[happydns.UserAuth](&s.authUsers), nil
}
// ListAuthUsers retrieves the list of known Users.
func (s *InMemoryStorage) ListAuthUsers() (happydns.UserAuths, error) {
s.mu.Lock()
defer s.mu.Unlock()
var users happydns.UserAuths
for _, user := range s.authUsers {
users = append(users, user)
}
return users, nil
}
// GetAuthUser retrieves the User with the given identifier.
func (s *InMemoryStorage) GetAuthUser(id happydns.Identifier) (*happydns.UserAuth, error) {
s.mu.Lock()
defer s.mu.Unlock()
user, exists := s.authUsers[id.String()]
if !exists {
return nil, happydns.ErrAuthUserNotFound
}
return user, nil
}
// GetAuthUserByEmail retrieves the User with the given email address.
func (s *InMemoryStorage) GetAuthUserByEmail(email string) (*happydns.UserAuth, error) {
s.mu.Lock()
defer s.mu.Unlock()
userid, exists := s.authUsersByEmail[email]
if !exists {
return nil, happydns.ErrAuthUserNotFound
}
user, exists := s.authUsers[userid.String()]
if !exists {
return nil, happydns.ErrAuthUserNotFound
}
return user, nil
}
// AuthUserExists checks if the given email address is already associated to an User.
func (s *InMemoryStorage) AuthUserExists(email string) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
_, exists := s.authUsersByEmail[email]
return exists, nil
}
// CreateAuthUser creates a record in the database for the given User.
func (s *InMemoryStorage) CreateAuthUser(user *happydns.UserAuth) (err error) {
s.mu.Lock()
defer s.mu.Unlock()
user.Id, err = happydns.NewRandomIdentifier()
s.authUsers[user.Id.String()] = user
s.authUsersByEmail[user.Email] = user.Id
return
}
// UpdateAuthUser updates the fields of the given User.
func (s *InMemoryStorage) UpdateAuthUser(user *happydns.UserAuth) error {
s.mu.Lock()
defer s.mu.Unlock()
s.authUsers[user.Id.String()] = user
s.authUsersByEmail[user.Email] = user.Id
return nil
}
// DeleteAuthUser removes the given User from the database.
func (s *InMemoryStorage) DeleteAuthUser(user *happydns.UserAuth) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.authUsers, user.Id.String())
delete(s.authUsersByEmail, user.Email)
return nil
}
// ClearAuthUsers deletes all AuthUsers present in the database.
func (s *InMemoryStorage) ClearAuthUsers() error {
s.mu.Lock()
defer s.mu.Unlock()
s.authUsers = make(map[string]*happydns.UserAuth)
s.authUsersByEmail = make(map[string]happydns.Identifier)
return nil
}

View file

@ -0,0 +1,34 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package inmemory
import (
"git.happydns.org/happyDomain/internal/storage"
)
func init() {
storage.StorageEngines["inmemory"] = Instantiate
}
func Instantiate() (storage.Storage, error) {
return NewInMemoryStorage()
}

View file

@ -0,0 +1,87 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package inmemory
import (
"log"
"sync"
"time"
"git.happydns.org/happyDomain/model"
)
// InMemoryStorage implements the Storage interface using in-memory data structures.
type InMemoryStorage struct {
mu sync.Mutex
authUsers map[string]*happydns.UserAuth
authUsersByEmail map[string]happydns.Identifier
domains map[string]*happydns.Domain
domainLogs map[string]*happydns.DomainLogWithDomainId
domainLogsByDomains map[string][]*happydns.Identifier
providers map[string]*happydns.ProviderMessage
sessions map[string]*happydns.Session
users map[string]*happydns.User
usersByEmail map[string]*happydns.User
zones map[string]*happydns.ZoneMessage
lastInsightsRun *time.Time
lastInsightsID happydns.Identifier
}
// NewInMemoryStorage creates a new instance of InMemoryStorage.
func NewInMemoryStorage() (*InMemoryStorage, error) {
return &InMemoryStorage{
authUsers: make(map[string]*happydns.UserAuth),
authUsersByEmail: make(map[string]happydns.Identifier),
domains: make(map[string]*happydns.Domain),
domainLogs: make(map[string]*happydns.DomainLogWithDomainId),
domainLogsByDomains: make(map[string][]*happydns.Identifier),
providers: make(map[string]*happydns.ProviderMessage),
sessions: make(map[string]*happydns.Session),
users: make(map[string]*happydns.User),
usersByEmail: make(map[string]*happydns.User),
zones: make(map[string]*happydns.ZoneMessage),
}, nil
}
// SchemaVersion returns the version of the migration currently in use.
func (s *InMemoryStorage) SchemaVersion() int {
return 0
}
// DoMigration is the first function called.
func (s *InMemoryStorage) MigrateSchema() error {
log.Println("YOU ARE USING THE inmemory STORAGE: DATA WILL BE LOST ON HAPPYDOMAIN STOP.")
// No migration needed for in-memory storage.
return nil
}
// Tidy should optimize the database, looking for orphan records, ...
func (s *InMemoryStorage) Tidy() error {
// No tidy needed for in-memory storage.
return nil
}
// Close shutdown the connection with the database and releases all structure.
func (s *InMemoryStorage) Close() error {
// No connection to close for in-memory storage.
return nil
}

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