├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── golangci-lint.yml
│ ├── release.yml
│ └── tests.yml
├── .gitignore
├── .golangci.yml
├── .idea
├── .gitignore
├── modules.xml
├── vcs.xml
└── vigilo.iml
├── .releaserc.json
├── .vscode
├── jsconfig.json
└── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── cmd
├── config
│ ├── application
│ │ ├── application_config.go
│ │ └── config.yaml
│ ├── audit
│ │ └── audit_config_yaml.go
│ ├── login
│ │ └── login_config_yaml.go
│ ├── password
│ │ └── password_config_yaml.go
│ ├── server
│ │ └── server_config_yaml.go
│ ├── smtp
│ │ └── smtp_config_yaml.go
│ └── token
│ │ └── token_config_yaml.go
└── identity-server
│ └── main.go
├── docs
├── contributing
│ └── README.md
└── user_guide
│ ├── README.md
│ ├── configuration
│ ├── configuration_guide.md
│ └── docker.md
│ └── identity
│ ├── README.md
│ └── endpoints
│ ├── admin_handler
│ └── get_audit_events.md
│ ├── authz_handler
│ └── authorize_client.md
│ ├── client_handler
│ ├── client_delete_request.md
│ ├── client_read_request.md
│ ├── client_registration.md
│ ├── client_secret_regeneration.md
│ └── client_update_request.md
│ ├── consent_handler
│ └── user_consent.md
│ ├── oidc_handler
│ ├── discovery.md
│ ├── jwks.md
│ └── user_info.md
│ ├── token_handler
│ ├── client_credentials_grant.md
│ ├── ropc_grant.md
│ ├── token_exchange.md
│ ├── token_introspection.md
│ ├── token_refresh.md
│ └── token_revocation.md
│ └── user_handler
│ ├── basic_user_authentication.md
│ ├── oauth_user_authentication.md
│ ├── password_reset.md
│ ├── password_reset_request.md
│ ├── user_logout.md
│ ├── user_registration.md
│ └── verify.md
├── go.mod
├── go.sum
├── go.work.sum
├── idp
├── config
│ ├── audit_config.go
│ ├── logging_config.go
│ ├── login_config.go
│ ├── password_config.go
│ ├── server_config.go
│ ├── smtp_config.go
│ └── token_config.go
└── server
│ └── identity_server.go
├── internal
├── background
│ ├── audit_jobs.go
│ ├── audit_jobs_test.go
│ ├── scheduler.go
│ ├── scheduler_test.go
│ ├── smtp_jobs.go
│ ├── smtp_jobs_test.go
│ ├── token_jobs.go
│ ├── token_jobs_test.go
│ ├── user_jobs.go
│ └── user_jobs_test.go
├── constants
│ ├── application_types.go
│ ├── audit.go
│ ├── claims.go
│ ├── context.go
│ ├── displays.go
│ ├── email.go
│ ├── env.go
│ ├── grant_types.go
│ ├── http.go
│ ├── ids.go
│ ├── oidc.go
│ ├── prompts.go
│ ├── response_types.go
│ ├── roles.go
│ └── timeouts.go
├── container
│ ├── container.go
│ ├── db_registry.go
│ ├── handler_registry.go
│ ├── lazy_init.go
│ ├── scheduler_registry.go
│ ├── server_config_registry.go
│ └── service_registry.go
├── domain
│ ├── audit
│ │ ├── model.go
│ │ ├── repository.go
│ │ └── service.go
│ ├── authorization
│ │ └── service.go
│ ├── authzcode
│ │ ├── creator.go
│ │ ├── issuer.go
│ │ ├── model.go
│ │ ├── repository.go
│ │ ├── service.go
│ │ ├── validation.go
│ │ └── validator.go
│ ├── claims
│ │ └── model.go
│ ├── client
│ │ ├── authenticator.go
│ │ ├── authorization.go
│ │ ├── creator.go
│ │ ├── manager.go
│ │ ├── model.go
│ │ ├── repository.go
│ │ └── validator.go
│ ├── cookies
│ │ └── service.go
│ ├── crypto
│ │ └── cryptographer.go
│ ├── email
│ │ ├── mailer.go
│ │ ├── model.go
│ │ └── service.go
│ ├── jwks
│ │ └── model.go
│ ├── jwt
│ │ └── service.go
│ ├── login
│ │ ├── repository.go
│ │ └── service.go
│ ├── oidc
│ │ ├── model.go
│ │ └── service.go
│ ├── session
│ │ ├── manager.go
│ │ ├── model.go
│ │ ├── repository.go
│ │ └── service.go
│ ├── token
│ │ ├── creator.go
│ │ ├── grant_processor.go
│ │ ├── issuer.go
│ │ ├── manager.go
│ │ ├── model.go
│ │ ├── parser.go
│ │ ├── repository.go
│ │ ├── validation.go
│ │ └── validator.go
│ ├── user
│ │ ├── authenticator.go
│ │ ├── creator.go
│ │ ├── manager.go
│ │ ├── model.go
│ │ ├── repository.go
│ │ ├── validation.go
│ │ └── verifier.go
│ └── userconsent
│ │ ├── model.go
│ │ ├── repository.go
│ │ └── service.go
├── errors
│ ├── codes.go
│ ├── collection.go
│ └── vigilo_errors.go
├── handlers
│ ├── admin_handler.go
│ ├── authz_handler.go
│ ├── client_handler.go
│ ├── consent_handler.go
│ ├── oidc_handler.go
│ ├── token_handler.go
│ └── user_handler.go
├── middleware
│ ├── middleware.go
│ ├── middleware_test.go
│ ├── rate_limiter.go
│ └── rate_limiter_test.go
├── mocks
│ ├── audit
│ │ ├── repository.go
│ │ └── service.go
│ ├── authorization
│ │ └── service.go
│ ├── authzcode
│ │ ├── creator.go
│ │ ├── issuer.go
│ │ ├── manager.go
│ │ ├── repository.go
│ │ └── validator.go
│ ├── client
│ │ ├── authenticator.go
│ │ ├── authorization.go
│ │ ├── creator.go
│ │ ├── manager.go
│ │ ├── repository.go
│ │ └── validator.go
│ ├── cookies
│ │ └── service.go
│ ├── crypto
│ │ └── cryptographer.go
│ ├── email
│ │ ├── mailer.go
│ │ └── service.go
│ ├── jwt
│ │ └── service.go
│ ├── login
│ │ ├── repository.go
│ │ └── service.go
│ ├── session
│ │ ├── manager.go
│ │ ├── repository.go
│ │ └── service.go
│ ├── token
│ │ ├── creator.go
│ │ ├── grant_processor.go
│ │ ├── issuer.go
│ │ ├── manager.go
│ │ ├── parser.go
│ │ ├── repository.go
│ │ └── validator.go
│ ├── user
│ │ ├── authenticator.go
│ │ ├── creator.go
│ │ ├── manager.go
│ │ ├── repository.go
│ │ └── verifier.go
│ └── userconsent
│ │ ├── repository.go
│ │ └── service.go
├── repository
│ ├── audit
│ │ └── memory.go
│ ├── authzcode
│ │ └── memory.go
│ ├── client
│ │ ├── memory.go
│ │ └── memory_test.go
│ ├── login
│ │ └── memory.go
│ ├── session
│ │ ├── memory.go
│ │ └── memory_test.go
│ ├── token
│ │ ├── memory.go
│ │ └── memory_test.go
│ ├── user
│ │ └── memory.go
│ └── userconsent
│ │ └── memory.go
├── routes
│ ├── route_group.go
│ ├── router_config.go
│ └── routes.go
├── service
│ ├── audit
│ │ └── service.go
│ ├── authorization
│ │ ├── service.go
│ │ └── service_test.go
│ ├── authzcode
│ │ ├── creator.go
│ │ ├── creator_test.go
│ │ ├── issuer.go
│ │ ├── issuer_test.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── validator.go
│ │ └── validator_test.go
│ ├── client
│ │ ├── authenticator.go
│ │ ├── authenticator_test.go
│ │ ├── authorization.go
│ │ ├── authorization_test.go
│ │ ├── creator.go
│ │ ├── creator_test.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── validator.go
│ │ └── validator_test.go
│ ├── cookies
│ │ ├── service.go
│ │ └── service_test.go
│ ├── crypto
│ │ ├── cryptographer.go
│ │ └── cryptographer_test.go
│ ├── email
│ │ ├── mailer.go
│ │ ├── service.go
│ │ ├── service_test.go
│ │ └── templates
│ │ │ ├── account_deletion.html
│ │ │ └── account_verification.html
│ ├── jwt
│ │ └── service.go
│ ├── login
│ │ ├── service.go
│ │ └── service_test.go
│ ├── oidc
│ │ ├── service.go
│ │ └── service_test.go
│ ├── session
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── service.go
│ │ └── service_test.go
│ ├── token
│ │ ├── creator.go
│ │ ├── creator_test.go
│ │ ├── grant_processor.go
│ │ ├── grant_processor_test.go
│ │ ├── issuer.go
│ │ ├── issuer_test.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── parser.go
│ │ ├── parser_test.go
│ │ ├── validator.go
│ │ └── validator_test.go
│ ├── user
│ │ ├── authenticator.go
│ │ ├── authenticator_test.go
│ │ ├── creator.go
│ │ ├── creator_test.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── verifier.go
│ │ └── verifier_test.go
│ └── userconsent
│ │ ├── service.go
│ │ └── service_test.go
├── types
│ ├── client_types.go
│ ├── code_challenge_methods.go
│ ├── scopes.go
│ ├── token_endpoint_auth_types.go
│ └── token_types.go
├── utils
│ ├── context.go
│ ├── helpers.go
│ └── logging.go
└── web
│ ├── decode_json.go
│ ├── endpoints.go
│ ├── header.go
│ └── responses.go
├── scripts
├── push_docker_dev.sh
├── run_docker_local.sh
└── update_imports.sh
├── tests
└── integration
│ ├── admin_handler_integration_test.go
│ ├── authz_handler_integration_test.go
│ ├── client_handler_integration_test.go
│ ├── consent_handler_integration_test.go
│ ├── oidc_handler_integration_test.go
│ ├── rate_limiting_test.go
│ ├── token_handler_integration_test.go
│ ├── user_handler_integration_test.go
│ └── vigilo_test_context.go
└── ui
├── eslint.config.mjs
├── package-lock.json
├── package.json
├── public
└── index.html
├── src
├── App.jsx
├── api
│ ├── clientApi.js
│ └── userApi.js
├── components
│ ├── BaseButton.jsx
│ ├── Container.jsx
│ ├── FlexContainer.jsx
│ └── FormInput.jsx
├── constants
│ ├── endpoints.js
│ └── urlParams.js
├── context
│ └── ApplicationContext.jsx
├── forms
│ ├── AuthenticationForm.jsx
│ ├── ConsentForm.jsx
│ └── InvalidRedirectErrorPage.jsx
├── index.css
├── index.js
├── pages
│ ├── AuthenticationPage.jsx
│ ├── ConsentPage.jsx
│ └── ErrorPage.jsx
├── popups
│ ├── AuthenticationPopup.jsx
│ └── ConsentPopup.jsx
└── styles
│ ├── AuthenticationPage.scss
│ ├── ConsentPage.scss
│ ├── ErrorPage.scss
│ ├── forms.scss
│ └── popup.scss
└── tailwind.config.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 | A clear and concise description of what the bug is.
12 |
13 | ---
14 |
15 | ### Steps to reproduce
16 |
17 | ---
18 |
19 | ### Expected behavior
20 | A clear and concise description of what you expected to happen.
21 |
22 | ---
23 |
24 | ### Screenshots
25 | If applicable, add screenshots to help explain your problem.
26 |
27 | ---
28 |
29 | ### Additional context
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 |
3 | on:
4 | pull_request:
5 | branches: [ '*' ]
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | golangci:
12 | name: lint
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-go@v5
17 | with:
18 | go-version-file: ./go.mod
19 | - name: golangci-lint
20 | uses: golangci/golangci-lint-action@v8
21 | with:
22 | version: 'latest'
23 |
24 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 |
3 | on:
4 | pull_request:
5 | branches: [ '*' ]
6 |
7 | jobs:
8 | run-tests:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v5
17 | with:
18 | go-version: stable
19 |
20 | - name: Create .env.test file
21 | run: |
22 | echo "SMTP_USERNAME=${{ secrets.SMTP_USERNAME }}" >> .env.test
23 | echo "SMTP_FROM_ADDRESS=${{ secrets.SMTP_FROM_ADDRESS }}" >> .env.test
24 | echo "SMTP_PASSWORD=${{ secrets.SMTP_PASSWORD }}" >> .env.test
25 | echo "TOKEN_ISSUER=${{ secrets.TOKEN_ISSUER }}" >> .env.test
26 | echo "TOKEN_PRIVATE_KEY=${{ secrets.TOKEN_PRIVATE_KEY }}" >> .env.test
27 | echo "TOKEN_PUBLIC_KEY=${{ secrets.TOKEN_PUBLIC_KEY }}" >> .env.test
28 | echo "CRYPTO_SECRET_KEY=${{ secrets.CRYPTO_SECRET_KEY }}" >> .env.test
29 |
30 | - name: Install necessary dependencies
31 | run: go mod tidy
32 |
33 | - name: Format code
34 | run: gofmt -s -w .
35 |
36 | - name: Run tests with coverage
37 | run: go test -coverprofile=coverage.out ./internal/... ./tests/integration/...
38 |
39 | - name: Update coverage report
40 | uses: ncruces/go-coverage-report@v0
41 | with:
42 | report: true
43 | chart: true
44 | amend: true
45 | if: |
46 | github.event_name == 'pull_request'
47 | continue-on-error: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 | go.work.sum
23 |
24 | # env file
25 | .env
26 |
27 | /ui/node_modules/
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | run:
4 | timeout: 5m
5 |
6 | linters:
7 | enable:
8 | - govet
9 | - errcheck
10 | - staticcheck
11 | - unused
12 | - ineffassign
13 | - bodyclose
14 | - contextcheck
15 | - containedctx
16 | - errorlint
17 | - gocognit
18 | - goconst
19 | - godox
20 | - maintidx
21 | - misspell
22 | - mnd
23 | - nestif
24 | - sqlclosecheck
25 | - testifylint
26 | - unparam
27 | - whitespace
28 | - wrapcheck
29 |
30 | formatters:
31 | enable:
32 | - gofmt
33 | - goimports
34 |
35 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vigilo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": ["master"],
3 | "plugins": [
4 | ["@semantic-release/commit-analyzer", {
5 | "preset": "angular",
6 | "releaseRules": [
7 | {"type": "breaking", "release": "major"},
8 | {"type": "feat", "release": "minor"},
9 | {"type": "fix", "release": "patch"},
10 |
11 | {"type": "task", "release": false},
12 | {"type": "test", "release": false},
13 | {"type": "refactor", "release": false},
14 | {"type": "perf", "release": false},
15 | {"type": "docs", "release": false},
16 | {"type": "chore", "release": false}
17 | ]
18 | }],
19 | "@semantic-release/release-notes-generator",
20 | "@semantic-release/github"
21 | ]
22 | }
--------------------------------------------------------------------------------
/.vscode/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "jsx": "react-jsx"
5 | },
6 | "exclude": ["node_modules"]
7 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "GORELEASER"
4 | ],
5 | "editor.codeActionsOnSave": {
6 | "source.fixAll.eslint": "always"
7 | },
8 | "go.formatTool": "goimports",
9 | "[go]": {
10 | "editor.defaultFormatter": "golang.go"
11 | },
12 | "files.autoSave": "onFocusChange",
13 | "editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
14 | "editor.formatOnType": false,
15 | "editor.formatOnPaste": true,
16 | "editor.formatOnSaveMode": "file",
17 | "vs-code-prettier-eslint.prettierLast": false,
18 | "editor.formatOnSave": true,
19 | "eslint.format.enable": true,
20 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
21 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20-alpine AS ui-builder
2 | WORKDIR /app
3 | COPY ui/ ./
4 | RUN npm install
5 | RUN npm run build
6 |
7 | FROM golang:latest AS builder
8 | WORKDIR /app
9 | COPY . .
10 |
11 | ENV GOOS=linux
12 | ENV GOARCH=amd64
13 | ENV CGO_ENABLED=0
14 |
15 | RUN go build -o /app/identity-server ./cmd/identity-server
16 |
17 | FROM alpine:latest
18 | WORKDIR /app
19 | COPY --from=builder /app/identity-server .
20 | COPY --from=builder /app/cmd/config/application/config.yaml ./config.yaml
21 | COPY --from=ui-builder /app/build ./ui/build
22 |
23 | RUN chmod +x ./identity-server
24 | EXPOSE 8080
25 |
26 | ENV VIGILO_SERVER_MODE=docker
27 | ENV REACT_BUILD_PATH=/app/ui/build
28 |
29 | CMD ["./identity-server"]
30 |
--------------------------------------------------------------------------------
/cmd/config/application/config.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vigiloauth/vigilo/80980e8b5728750c4df0453953fcbba44e9cf098/cmd/config/application/config.yaml
--------------------------------------------------------------------------------
/cmd/config/audit/audit_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | )
8 |
9 | const oneDay time.Duration = 24 * time.Hour
10 |
11 | type AuditLogConfigYAML struct {
12 | RetentionPeriod *int `yaml:"retention_period,omitempty"`
13 | }
14 |
15 | func (alc *AuditLogConfigYAML) ToOptions() []config.AuditLogConfigOptions {
16 | options := []config.AuditLogConfigOptions{}
17 |
18 | if alc.RetentionPeriod != nil {
19 | retention := time.Duration(*alc.RetentionPeriod) * oneDay
20 | options = append(options, config.WithRetentionPeriod(retention))
21 | }
22 |
23 | return options
24 | }
25 |
--------------------------------------------------------------------------------
/cmd/config/login/login_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | )
8 |
9 | type LoginConfigYAML struct {
10 | MaxFailedAttempts *int `yaml:"max_failed_attempts,omitempty"`
11 | Delay *int64 `yaml:"delay,omitempty"`
12 | LoginURL *string `yaml:"login_url,omitempty"`
13 | }
14 |
15 | func (lc *LoginConfigYAML) ToOptions() []config.LoginConfigOptions {
16 | options := []config.LoginConfigOptions{}
17 |
18 | if lc.MaxFailedAttempts != nil {
19 | options = append(options, config.WithMaxFailedAttempts(*lc.MaxFailedAttempts))
20 | }
21 |
22 | if lc.Delay != nil {
23 | delay := time.Duration(*lc.Delay) * time.Millisecond
24 | options = append(options, config.WithDelay(delay))
25 | }
26 |
27 | if lc.LoginURL != nil {
28 | options = append(options, config.WithLoginURL(*lc.LoginURL))
29 | }
30 |
31 | return options
32 | }
33 |
--------------------------------------------------------------------------------
/cmd/config/password/password_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/vigiloauth/vigilo/v2/idp/config"
4 |
5 | type PasswordConfigYAML struct {
6 | RequireUppercase *bool `yaml:"require_uppercase,omitempty"`
7 | RequireNumber *bool `yaml:"require_number,omitempty"`
8 | RequireSymbol *bool `yaml:"require_symbol,omitempty"`
9 | MinimumLength *int `yaml:"minimum_length,omitempty"`
10 | }
11 |
12 | func (pc *PasswordConfigYAML) ToOptions() []config.PasswordConfigOptions {
13 | options := []config.PasswordConfigOptions{}
14 |
15 | if pc.RequireUppercase != nil {
16 | options = append(options, config.WithUppercase())
17 | }
18 |
19 | if pc.RequireNumber != nil {
20 | options = append(options, config.WithNumber())
21 | }
22 |
23 | if pc.RequireSymbol != nil {
24 | options = append(options, config.WithSymbol())
25 | }
26 |
27 | if pc.MinimumLength != nil {
28 | options = append(options, config.WithMinLength(*pc.MinimumLength))
29 | }
30 |
31 | return options
32 | }
33 |
--------------------------------------------------------------------------------
/cmd/config/server/server_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | )
8 |
9 | type ServerConfigYAML struct {
10 | Port *string `yaml:"port,omitempty"`
11 | CertFilePath *string `yaml:"cert_file_path,omitempty"`
12 | KeyFilePath *string `yaml:"key_file_path,omitempty"`
13 | SessionCookieName *string `yaml:"session_cookie_name,omitempty"`
14 | ForceHTTPS *bool `yaml:"force_https,omitempty"`
15 | Domain *string `yaml:"domain,omitempty"`
16 | EnableRequestLogging *bool `yaml:"enable_request_logging,omitempty"`
17 |
18 | ReadTimeout *int64 `yaml:"read_timeout,omitempty"`
19 | WriteTimeout *int64 `yaml:"write_timeout,omitempty"`
20 | AuthzCodeDuration *int64 `yaml:"authorization_code_duration,omitempty"`
21 | }
22 |
23 | func (sc *ServerConfigYAML) ToOptions() []config.ServerConfigOptions {
24 | options := []config.ServerConfigOptions{}
25 |
26 | if sc.Port != nil {
27 | options = append(options, config.WithPort(*sc.Port))
28 | }
29 | if sc.CertFilePath != nil {
30 | options = append(options, config.WithCertFilePath(*sc.CertFilePath))
31 | }
32 | if sc.KeyFilePath != nil {
33 | options = append(options, config.WithKeyFilePath(*sc.KeyFilePath))
34 | }
35 | if sc.SessionCookieName != nil {
36 | options = append(options, config.WithSessionCookieName(*sc.SessionCookieName))
37 | }
38 | if sc.ForceHTTPS != nil {
39 | options = append(options, config.WithForceHTTPS())
40 | }
41 | if sc.EnableRequestLogging != nil {
42 | options = append(options, config.WithRequestLogging(*sc.EnableRequestLogging))
43 | }
44 | if sc.ReadTimeout != nil {
45 | timeoutDuration := time.Duration(*sc.ReadTimeout) * time.Second
46 | options = append(options, config.WithReadTimeout(timeoutDuration))
47 | }
48 | if sc.WriteTimeout != nil {
49 | timeoutDuration := time.Duration(*sc.WriteTimeout) * time.Second
50 | options = append(options, config.WithWriteTimeout(timeoutDuration))
51 | }
52 | if sc.AuthzCodeDuration != nil {
53 | duration := time.Duration(*sc.AuthzCodeDuration) * time.Minute
54 | options = append(options, config.WithAuthorizationCodeDuration(duration))
55 | }
56 | if sc.Domain != nil {
57 | options = append(options, config.WithDomain(*sc.Domain))
58 | }
59 |
60 | return options
61 | }
62 |
--------------------------------------------------------------------------------
/cmd/config/smtp/smtp_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/vigiloauth/vigilo/v2/idp/config"
4 |
5 | const TLSPort int = 587
6 | const SSLPort int = 465
7 |
8 | type SMTPConfigYAML struct {
9 | Host *string `yaml:"host,omitempty"`
10 | Port *int `yaml:"port,omitempty"`
11 | Username *string `yaml:"username,omitempty"`
12 | Password *string `yaml:"password,omitempty"`
13 | FromAddress *string `yaml:"from_address,omitempty"`
14 | Encryption *string `yaml:"encryption,omitempty"`
15 | }
16 |
17 | func (s *SMTPConfigYAML) ToOptions() []config.SMTPConfigOptions {
18 | options := []config.SMTPConfigOptions{}
19 |
20 | if s.Host != nil {
21 | options = append(options, config.WithSMTPHost(*s.Host))
22 | }
23 |
24 | if s.Port != nil {
25 | switch *s.Port {
26 | case TLSPort:
27 | options = append(options, config.WithTLS())
28 | case SSLPort:
29 | options = append(options, config.WithSSL())
30 | }
31 | }
32 |
33 | if s.Username != nil && s.Password != nil {
34 | options = append(options, config.WithCredentials(*s.Username, *s.Password))
35 | }
36 |
37 | if s.FromAddress != nil {
38 | options = append(options, config.WithFromAddress(*s.FromAddress))
39 | }
40 |
41 | if s.Encryption != nil {
42 | options = append(options, config.WithEncryption(*s.Encryption))
43 | }
44 |
45 | return options
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/config/token/token_config_yaml.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | )
8 |
9 | const oneDay time.Duration = 24 * time.Hour
10 |
11 | type TokenConfigYAML struct {
12 | SecretKey *string `yaml:"secret_key,omitempty"`
13 | ExpirationTime *int64 `yaml:"expiration_time,omitempty"`
14 | AccessTokenDuration *int64 `yaml:"access_token_duration,omitempty"`
15 | RefreshTokenDuration *int64 `yaml:"refresh_token_duration,omitempty"`
16 | }
17 |
18 | func (tc *TokenConfigYAML) ToOptions() []config.TokenConfigOptions {
19 | options := []config.TokenConfigOptions{}
20 |
21 | if tc.ExpirationTime != nil {
22 | duration := time.Duration(*tc.ExpirationTime) * time.Minute
23 | options = append(options, config.WithExpirationTime(duration))
24 | }
25 |
26 | if tc.AccessTokenDuration != nil {
27 | duration := time.Duration(*tc.AccessTokenDuration) * time.Minute
28 | options = append(options, config.WithAccessTokenDuration(duration))
29 | }
30 |
31 | if tc.RefreshTokenDuration != nil {
32 | duration := time.Duration(*tc.RefreshTokenDuration) * (oneDay)
33 | options = append(options, config.WithRefreshTokenDuration(duration))
34 | }
35 |
36 | return options
37 | }
38 |
--------------------------------------------------------------------------------
/docs/user_guide/identity/endpoints/oidc_handler/discovery.md:
--------------------------------------------------------------------------------
1 | # Discovery Endpoint
2 |
3 | ## Endpoint
4 | ```http
5 | GET /identity/oauth2/.well-known/openid-configuration
6 | ```
7 |
8 | ## Headers
9 | | Key | Value | Description |
10 | | :-------------- | :---------------------------- | :---------------------------------------- |
11 | | Content-Type | application/json | Indicates that the request body is JSON. |
12 | | Date | Tue, 03 Dec 2024 19:38:16 GMT | The date and time the request was made. |
13 | | Content-Length | [Content-Length] | The length of the request body in bytes. |
14 |
15 | ---
16 |
17 | ## Example Request
18 | ```http
19 | GET /identity/oauth2/.well-known/openid-configuration HTTP/1.1
20 | ```
21 |
22 | ## Responses
23 |
24 | #### HTTP Status Code: `200 OK`
25 | #### Response Body:
26 | ```json
27 | {
28 | "issuer": "https://auth.example.com",
29 | "authorization_endpoint": "https://auth.example.com/oauth2/authorize",
30 | "token_endpoint": "https://auth.example.com/oauth2/token",
31 | "user_info_endpoint": "https://auth.example.com/oauth2/userinfo",
32 | "jwks_uri": "https://auth.example.com/oauth2/.well-known/jwks.json",
33 | "registration_endpoint": "https://auth.example.com/client/register",
34 | "scopes_supported": [
35 | "clients:manage",
36 | "clients:read",
37 | "clients:write",
38 | "clients:delete",
39 | "users:manage",
40 | "users:read",
41 | "users:write",
42 | "users:delete",
43 | "tokens:revoke",
44 | "tokens:introspect",
45 | "oidc",
46 | "email",
47 | "address",
48 | "profile",
49 | "phone"
50 | "offline_access",
51 | ],
52 | "response_types_supported": [
53 | "id_token",
54 | "code",
55 | "token"
56 | ],
57 | "grant_types_supported": [
58 | "refresh_token",
59 | "implicit_flow",
60 | "password",
61 | "authorization_code",
62 | "pkce",
63 | "client_credentials",
64 | "device_code"
65 | ],
66 | "subject_types_supported": ["public"],
67 | "id_token_signing_alg_values_supported": ["RS256"],
68 | "id_token_encryption_alg_values_supported": ["RSA-OAEP"],
69 | "token_endpoint_auth_methods_supported": [
70 | "client_secret_basic",
71 | "client_secret_post",
72 | "none"
73 | ]
74 | }
75 | ```
--------------------------------------------------------------------------------
/docs/user_guide/identity/endpoints/oidc_handler/jwks.md:
--------------------------------------------------------------------------------
1 | # JWKS Endpoint
2 |
3 | ## Endpoint
4 | ```http
5 | GET /identity/oauth2/.well-known/jwks.json
6 | ```
7 |
8 | ## Headers
9 | | Key | Value | Description |
10 | | :-------------- | :---------------------------- | :---------------------------------------- |
11 | | Content-Type | application/json | Indicates that the request body is JSON. |
12 | | Date | Tue, 03 Dec 2024 19:38:16 GMT | The date and time the request was made. |
13 | | Content-Length | [Content-Length] | The length of the request body in bytes. |
14 |
15 | ---
16 |
17 | ## Example Request
18 | ```http
19 | GET /identity/oauth2/.well-known/jwks.json HTTP/1.1
20 | ```
21 |
22 | ## Responses
23 |
24 | #### HTTP Status Code: `200 OK`
25 | #### Response Body:
26 | ```json
27 | {
28 | "keys": [
29 | {
30 | "kty": "RSA",
31 | "kid": "abc123",
32 | "use": "sig",
33 | "alg": "RS256",
34 | "n": "modulus-base64url",
35 | "e": "exponent-base64url"
36 | }
37 | ]
38 | }
39 | ```
--------------------------------------------------------------------------------
/docs/user_guide/identity/endpoints/user_handler/basic_user_authentication.md:
--------------------------------------------------------------------------------
1 | # Basic User Authentication
2 |
3 | ## Endpoint
4 | ```
5 | POST /identity/auth/login
6 | ```
7 |
8 | ---
9 |
10 | ### Headers
11 | | Key | Value | Description |
12 | | :-------------- | :---------------------------- | :-----------------------------------------|
13 | | Content-Type | application/json | Indicates that the request body is JSON. |
14 | | Date | Tue, 03 Dec 2024 19:38:16 GMT | The date and time the request was made. |
15 | | Content-Length | [Content-Length] | The length of the request body in bytes. |
16 |
17 | ---
18 |
19 | ### Request Body
20 | | Field | Type | Required | Description |
21 | |:----------|:--------|:----------|:-------------------------------|
22 | | `email` | `string` | Yes | The user's email address. |
23 | | `password` | `string` | Yes | The password for the account. |
24 |
25 | ---
26 |
27 | ### Example Request
28 | ```json
29 | {
30 | "email": "john.doe@email.com",
31 | "password": "Pas$_w0rds"
32 | }
33 | ```
34 |
35 | ---
36 |
37 | ## Responses
38 | #### HTTP Status Code: `200 OK`
39 | #### Response Body:
40 | ```json
41 | {
42 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
43 | }
44 | ```
45 |
46 | ---
47 |
48 | ## Error Responses
49 | ### 1. Missing Email Field
50 | #### HTTP Status Code: `404 Not Found`
51 | #### Response Body:
52 | ```json
53 | {
54 | "error": "user_not_found",
55 | "error_description": "user not found."
56 | }
57 | ```
58 |
59 | ### 2. Invalid Password
60 | #### HTTP Status Code: `401 Unauthorized`
61 | #### Response Body:
62 | ```json
63 | {
64 | "error": "invalid_credentials",
65 | "error_description": "invalid Credentials"
66 | }
67 | ```
--------------------------------------------------------------------------------
/docs/user_guide/identity/endpoints/user_handler/user_logout.md:
--------------------------------------------------------------------------------
1 | # User Logout
2 | ## Endpoint
3 | ```
4 | POST /identity/auth/logout
5 | ```
6 | ---
7 |
8 | ### Headers
9 | | Key | Value | Description |
10 | | :-------------- | :---------------------------- | :----------------------------------------- |
11 | | Content-Type | application/json | Indicates that the request body is JSON. |
12 | | Date | Tue, 03 Dec 2024 19:38:16 GMT | The date and time the request was made. |
13 | | Content-Length | [Content-Length] | The length of the request body in bytes. |
14 |
15 | ---
16 |
17 | ## Example Request
18 | ```http
19 | POST /identity/auth/logout HTTP/1.1
20 | Authorization: Bearer reg-23410913-abewfq.123483
21 | ```
22 |
23 | ---
24 |
25 | ## Responses
26 | #### HTTP Status Code: `200 OK`
27 | #### Response Headers:
28 | | Key | Value |
29 | |:----------------|-------------------------------|
30 | | Content-Type | application/json |
31 | | Date | Tue, 03 Dec 2024 19:38:16 GMT |
32 | ---
33 |
34 | ## Error Responses
35 | ### 1. Missing Authorization Header
36 | #### HTTP Status Code: `401 Unauthorized`
37 | #### Response Body:
38 | ```json
39 | {
40 | "error": "invalid_credentials",
41 | "error_description": "invalid credentials"
42 | }
43 | ```
44 |
45 | ### 2. Invalid Token
46 | #### HTTP Status Code: `401 Unauthorized`
47 | #### Response Body:
48 | ```json
49 | {
50 | "error": "invalid_credentials",
51 | "error_description": "invalid credentials"
52 | }
53 | ```
--------------------------------------------------------------------------------
/docs/user_guide/identity/endpoints/user_handler/verify.md:
--------------------------------------------------------------------------------
1 | # Verify Account
2 |
3 | ## Endpoint
4 | ```http
5 | GET /identity/auth/verify
6 | ```
7 |
8 | ---
9 |
10 | **Description:**
11 | This endpoint is used when a new user needs to verify their email. During the registration process, the user will receive an email to verify their account.
12 |
13 | ---
14 |
15 | ## Query Parameters
16 | | Parameter | Type | Required | Description |
17 | :-----------------|:------------|:------------|:--------------------------------------------------|
18 | | `token` | `string` | yes | The verification code the user receives by email. |
19 |
20 | ---
21 |
22 | ## Example Request
23 | ```http
24 | GET /identity/auth/verify HTTP/1.1
25 | token=czZCaGRSa3F0MzpnWDFmQmF0M2JW
26 | ```
27 |
28 | ---
29 |
30 | ## Responses
31 |
32 | ### Success Response
33 | #### HTTP Status Code: `200 OK`
34 | >*Note:* There is no response body for successful validation.
35 |
36 | ---
37 |
38 | ## Error Responses
39 |
40 | ### 1. Missing Required Parameter
41 | #### HTTP Status Code: `400 Bad Request`
42 | #### Response Body:
43 | ```json
44 | {
45 | "error": "invalid_request",
46 | "error_description": "failed to validate user account",
47 | "error_details": "missing one or more required parameters in the request"
48 | }
49 | ```
50 |
51 | ### 2. Invalid Verification Code
52 | #### HTTP Status Code: `401 Unauthorized`
53 | #### Response Body:
54 | ```json
55 | {
56 | "error": "unauthorized",
57 | "error_description": "failed to validate user account",
58 | "error_details": "the verification code is either expired or does not exist"
59 | }
60 | ```
61 |
62 | ### 3. Verification Code does not match with the User
63 | #### HTTP Status Code: `401 Unauthorized`
64 | #### Response Body:
65 | ```json
66 | {
67 | "error": "unauthorized",
68 | "error_description": "failed to validate user account",
69 | "error_details": "the verification code is invalid"
70 | }
71 | ```
72 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vigiloauth/vigilo/v2
2 |
3 | go 1.23.3
4 |
5 | require (
6 | github.com/go-chi/chi/v5 v5.2.1
7 | github.com/joho/godotenv v1.5.1
8 | github.com/stretchr/testify v1.10.0
9 | golang.org/x/crypto v0.35.0
10 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
11 | )
12 |
13 | require gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
14 |
15 | require (
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/go-chi/cors v1.2.1
18 | github.com/golang-jwt/jwt v3.2.2+incompatible
19 | github.com/google/uuid v1.6.0
20 | github.com/pmezard/go-difflib v1.0.0 // indirect
21 | gopkg.in/yaml.v3 v3.0.1
22 | )
23 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
4 | github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
5 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
6 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
7 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
8 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
9 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
10 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
11 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
12 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
16 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
17 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
18 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
19 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
20 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
23 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
24 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
27 |
--------------------------------------------------------------------------------
/go.work.sum:
--------------------------------------------------------------------------------
1 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
2 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
3 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
4 | github.com/vigiloauth/vigilo v0.0.0/go.mod h1:sKUpLR3KmsbEqNb0rJrOWvdNSCWpYAsEXkI9crTjRuw=
5 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
6 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
7 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
8 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
9 | golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
10 | golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
11 | golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
12 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
13 |
--------------------------------------------------------------------------------
/idp/config/audit_config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "time"
4 |
5 | // AuditLogConfig holds configuration settings for audit logging,
6 | // such as how long to retain audit logs.
7 | type AuditLogConfig struct {
8 | retentionPeriod time.Duration
9 | }
10 |
11 | // AuditLogConfigOptions defines a function signature for modifying AuditLogConfig.
12 | type AuditLogConfigOptions func(*AuditLogConfig)
13 |
14 | // Default retention period for audit logs: 90 days.
15 | const defaultRetentionPeriod time.Duration = 90 * 24 * time.Hour
16 |
17 | // NewAuditLogConfig creates a new AuditLogConfig instance,
18 | // applying any provided options to override the default values.
19 | func NewAuditLogConfig(opts ...AuditLogConfigOptions) *AuditLogConfig {
20 | cfg := &AuditLogConfig{retentionPeriod: defaultRetentionPeriod}
21 | for _, opt := range opts {
22 | opt(cfg)
23 | }
24 | return cfg
25 | }
26 |
27 | // WithRetentionPeriod returns an option that sets a custom retention period
28 | // for audit logs. Use this with NewAuditLogConfig to override the default.
29 | func WithRetentionPeriod(retentionPeriod time.Duration) AuditLogConfigOptions {
30 | return func(alc *AuditLogConfig) {
31 | alc.retentionPeriod = retentionPeriod
32 | }
33 | }
34 |
35 | // RetentionPeriod returns the configured audit log retention period.
36 | func (alc *AuditLogConfig) RetentionPeriod() time.Duration {
37 | return alc.retentionPeriod
38 | }
39 |
--------------------------------------------------------------------------------
/internal/background/audit_jobs.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/vigiloauth/vigilo/v2/idp/config"
8 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/audit"
9 | )
10 |
11 | type AuditJobs struct {
12 | auditLogger domain.AuditLogger
13 | retentionPeriod time.Duration
14 | purgeInterval time.Duration
15 | logger *config.Logger
16 | module string
17 | }
18 |
19 | func NewAuditJobs(auditLogger domain.AuditLogger, retentionPeriod, purgeInterval time.Duration) *AuditJobs {
20 | return &AuditJobs{
21 | auditLogger: auditLogger,
22 | retentionPeriod: retentionPeriod,
23 | purgeInterval: purgeInterval,
24 | logger: config.GetServerConfig().Logger(),
25 | module: "Audit Jobs",
26 | }
27 | }
28 |
29 | func (a *AuditJobs) PurgeLogs(ctx context.Context) {
30 | a.logger.Info(a.module, "", "[PurgeLogs]: Starting process of removing old audit logs")
31 | ticker := time.NewTicker(a.purgeInterval)
32 | defer ticker.Stop()
33 |
34 | for {
35 | select {
36 | case <-ticker.C:
37 | cutoff := time.Now().Add(-a.retentionPeriod)
38 | if err := a.auditLogger.DeleteOldEvents(ctx, cutoff); err != nil {
39 | a.logger.Error(a.module, "", "[PurgeLogs]: There was an error deleting old audit logs: %v", err)
40 | continue //nolint
41 | }
42 | case <-ctx.Done():
43 | a.logger.Info(a.module, "", "[PurgeLogs]: Stopping the process of deleting old audit logs")
44 | return
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/internal/background/audit_jobs_test.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | mocks "github.com/vigiloauth/vigilo/v2/internal/mocks/audit"
11 | )
12 |
13 | func TestAuditJobs_PurgeEvents(t *testing.T) {
14 | var mu sync.Mutex
15 | deleteCalls := 0
16 |
17 | auditLogger := &mocks.MockAuditLogger{
18 | DeleteOldEventsFunc: func(cts context.Context, olderThan time.Time) error {
19 | mu.Lock()
20 | defer mu.Unlock()
21 | deleteCalls++
22 | return nil
23 | },
24 | }
25 |
26 | interval := 50 * time.Millisecond
27 | jobs := NewAuditJobs(auditLogger, interval, interval)
28 | ctx, cancel := context.WithTimeout(context.TODO(), 500*time.Millisecond)
29 | defer cancel()
30 |
31 | var wg sync.WaitGroup
32 | wg.Add(1)
33 |
34 | go func() {
35 | defer wg.Done()
36 | jobs.PurgeLogs(ctx)
37 | }()
38 | <-ctx.Done()
39 | wg.Wait()
40 |
41 | assert.GreaterOrEqual(t, deleteCalls, 1, "Should have called DeleteOldEvents at least once")
42 | }
43 |
--------------------------------------------------------------------------------
/internal/background/scheduler.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/vigiloauth/vigilo/v2/idp/config"
8 | )
9 |
10 | type JobFunc func(ctx context.Context)
11 |
12 | type Scheduler struct {
13 | mu sync.RWMutex
14 | jobs []JobFunc
15 | wg sync.WaitGroup
16 | stopCh chan struct{}
17 | logger *config.Logger
18 | module string
19 | }
20 |
21 | func NewScheduler() *Scheduler {
22 | return &Scheduler{
23 | logger: config.GetServerConfig().Logger(),
24 | module: "Scheduler",
25 | stopCh: make(chan struct{}),
26 | }
27 | }
28 |
29 | func (s *Scheduler) RegisterJob(jobName string, job JobFunc) {
30 | s.mu.Lock()
31 | defer s.mu.Unlock()
32 | s.jobs = append(s.jobs, job)
33 | s.logger.Info(s.module, "", "[RegisterJob]: Registered job [%s]. Total jobs: %d", jobName, len(s.jobs))
34 | }
35 |
36 | func (s *Scheduler) StartJobs(ctx context.Context) {
37 | s.logger.Info(s.module, "", "[StartJobs]: Starting %d background jobs...", len(s.jobs))
38 | s.mu.RLock()
39 | defer s.mu.RUnlock()
40 |
41 | for i, job := range s.jobs {
42 | s.wg.Add(1)
43 | go func(i int, j JobFunc) {
44 | defer s.wg.Done()
45 | s.logger.Info(s.module, "", "[StartJobs]: Starting job #%d", i+1)
46 | j(ctx)
47 | }(i, job)
48 | }
49 |
50 | s.wg.Wait()
51 | s.logger.Info(s.module, "", "[StartJobs]: All background jobs completed.")
52 | }
53 |
54 | func (s *Scheduler) Stop() {
55 | close(s.stopCh)
56 | }
57 |
58 | func (s *Scheduler) Wait() {
59 | s.mu.Lock()
60 | defer s.mu.Unlock()
61 | s.logger.Info(s.module, "", "Waiting for all background jobs to finish...")
62 | s.wg.Wait()
63 | s.logger.Info(s.module, "", "All background jobs have finished.")
64 | }
65 |
66 | func (s *Scheduler) GetJobs() []JobFunc {
67 | s.mu.RLock()
68 | defer s.mu.RUnlock()
69 | return s.jobs
70 | }
71 |
--------------------------------------------------------------------------------
/internal/background/token_jobs.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/vigiloauth/vigilo/v2/idp/config"
8 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/token"
9 | )
10 |
11 | type TokenJobs struct {
12 | tokenService domain.TokenManager
13 | interval time.Duration
14 | logger *config.Logger
15 | module string
16 | }
17 |
18 | func NewTokenJobs(tokenService domain.TokenManager, interval time.Duration) *TokenJobs {
19 | return &TokenJobs{
20 | tokenService: tokenService,
21 | interval: interval,
22 | logger: config.GetServerConfig().Logger(),
23 | module: "Token Jobs",
24 | }
25 | }
26 |
27 | func (t *TokenJobs) DeleteExpiredTokens(ctx context.Context) {
28 | t.logger.Info(t.module, "", "[DeleteExpiredTokens]: Starting process of deleting expired tokens")
29 | ticker := time.NewTicker(t.interval)
30 | defer ticker.Stop()
31 |
32 | for {
33 | select {
34 | case <-ticker.C:
35 | if err := t.tokenService.DeleteExpiredTokens(ctx); err != nil {
36 | t.logger.Error(t.module, "", "[DeleteExpiredTokens]: An error occurred deleting expired tokens: %v", err)
37 | continue //nolint
38 | }
39 | case <-ctx.Done():
40 | t.logger.Info(t.module, "", "[DeleteExpiredTokens]: Stopping process of deleting expired tokens")
41 | return
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/internal/background/token_jobs_test.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | mocks "github.com/vigiloauth/vigilo/v2/internal/mocks/token"
11 | )
12 |
13 | func TestTokenJobs_DeleteExpiredTokens(t *testing.T) {
14 | var mu sync.Mutex
15 | deleteCalls := 0
16 |
17 | tokenService := &mocks.MockTokenManager{
18 | DeleteExpiredTokensFunc: func(ctx context.Context) error {
19 | mu.Lock()
20 | defer mu.Unlock()
21 | deleteCalls++
22 | return nil
23 | },
24 | }
25 |
26 | interval := 50 * time.Millisecond
27 | jobs := NewTokenJobs(tokenService, interval)
28 | ctx, cancel := context.WithTimeout(context.TODO(), 500*time.Millisecond)
29 | defer cancel()
30 |
31 | var wg sync.WaitGroup
32 | wg.Add(1)
33 |
34 | go func() {
35 | defer wg.Done()
36 | jobs.DeleteExpiredTokens(ctx)
37 | }()
38 | <-ctx.Done()
39 | wg.Wait()
40 |
41 | assert.GreaterOrEqual(t, deleteCalls, 1, "Should have called DeleteExpiredTokens at least once")
42 | }
43 |
--------------------------------------------------------------------------------
/internal/background/user_jobs.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/vigiloauth/vigilo/v2/idp/config"
8 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/user"
9 | )
10 |
11 | type UserJobs struct {
12 | userService domain.UserManager
13 | interval time.Duration
14 | logger *config.Logger
15 | module string
16 | }
17 |
18 | func NewUserJobs(userService domain.UserManager, interval time.Duration) *UserJobs {
19 | return &UserJobs{
20 | userService: userService,
21 | interval: interval,
22 | logger: config.GetServerConfig().Logger(),
23 | module: "User Jobs",
24 | }
25 | }
26 |
27 | func (u *UserJobs) DeleteUnverifiedUsers(ctx context.Context) {
28 | u.logger.Info(u.module, "", "[DeleteUnverifiedUsers]: Starting Process of deleting unverified users that were created over a week ago")
29 | ticker := time.NewTicker(u.interval)
30 | defer ticker.Stop()
31 |
32 | for {
33 | select {
34 | case <-ticker.C:
35 | if err := u.userService.DeleteUnverifiedUsers(ctx); err != nil {
36 | u.logger.Error(u.module, "", "[DeleteUnverifiedUsers]: Failed to delete unverified users: %v", err)
37 | continue //nolint:nlreturn
38 | }
39 |
40 | case <-ctx.Done():
41 | u.logger.Info(u.module, "", "[DeleteUnverifiedUsers]: Stopping process of deleting unverified users")
42 | return //nolint:nlreturn
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/internal/background/user_jobs_test.go:
--------------------------------------------------------------------------------
1 | package background
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | mocks "github.com/vigiloauth/vigilo/v2/internal/mocks/user"
11 | )
12 |
13 | func TestUserJobs_DeleteUnverifiedUsers(t *testing.T) {
14 | var mu sync.Mutex
15 | deleteCalls := 0
16 |
17 | userService := &mocks.MockUserManager{
18 | DeleteUnverifiedUsersFunc: func(ctx context.Context) error {
19 | mu.Lock()
20 | defer mu.Unlock()
21 | deleteCalls++
22 | return nil
23 | },
24 | }
25 |
26 | interval := 50 * time.Millisecond
27 | jobs := NewUserJobs(userService, interval)
28 | ctx, cancel := context.WithTimeout(context.TODO(), 500*time.Millisecond)
29 | defer cancel()
30 |
31 | var wg sync.WaitGroup
32 | wg.Add(1)
33 |
34 | go func() {
35 | defer wg.Done()
36 | jobs.DeleteUnverifiedUsers(ctx)
37 | }()
38 | <-ctx.Done()
39 | wg.Wait()
40 |
41 | assert.GreaterOrEqual(t, deleteCalls, 1, "Should have called DeleteUnverifiedUsers at least once")
42 | }
43 |
--------------------------------------------------------------------------------
/internal/constants/application_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Predefined client application types.
4 | const (
5 | WebApplicationType string = "web"
6 | NativeApplicationType string = "native"
7 | )
8 |
9 | var ValidApplicationTypes = map[string]bool{
10 | WebApplicationType: true,
11 | NativeApplicationType: true,
12 | }
13 |
--------------------------------------------------------------------------------
/internal/constants/audit.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Audit Event Field Keys define the keys used for logging and tracking
4 | // details of audit events in the application.
5 | const (
6 | ActionDetails string = "action" // Key for storing the action performed (e.g., "create", "update").
7 | MethodDetails string = "method" // Key for storing the method used (e.g., "POST", "GET").
8 | )
9 |
--------------------------------------------------------------------------------
/internal/constants/claims.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | SubClaim string = "sub"
5 | NameClaim string = "name"
6 | GivenNameClaim string = "given_name"
7 | FamilyNameClaim string = "family_name"
8 | MiddleNameClaim string = "middle_name"
9 | NicknameClaim string = "nickname"
10 | PreferredUsernameClaim string = "preferred_username"
11 | ProfileClaim string = "profile"
12 | PictureClaim string = "picture"
13 | WebsiteClaim string = "website"
14 | GenderClaim string = "gender"
15 | BirthdateClaim string = "birthdate"
16 | ZoneinfoClaim string = "zoneinfo"
17 | LocaleClaim string = "locale"
18 | EmailClaim string = "email"
19 | EmailVerifiedClaim string = "email_verified"
20 | PhoneNumberClaim string = "phone_number"
21 | PhoneNumberVerifiedClaim string = "phone_number_verified"
22 | UpdatedAtClaim string = "updated_at"
23 | AddressClaim string = "address"
24 | )
25 |
26 | var SupportedClaims = map[string]bool{
27 | SubClaim: true,
28 | NameClaim: true,
29 | GivenNameClaim: true,
30 | FamilyNameClaim: true,
31 | MiddleNameClaim: true,
32 | NicknameClaim: true,
33 | PreferredUsernameClaim: true,
34 | ProfileClaim: true,
35 | PictureClaim: true,
36 | WebsiteClaim: true,
37 | GenderClaim: true,
38 | BirthdateClaim: true,
39 | ZoneinfoClaim: true,
40 | LocaleClaim: true,
41 | EmailClaim: true,
42 | EmailVerifiedClaim: true,
43 | PhoneNumberClaim: true,
44 | PhoneNumberVerifiedClaim: true,
45 | UpdatedAtClaim: true,
46 | AddressClaim: true,
47 | }
48 |
--------------------------------------------------------------------------------
/internal/constants/context.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // ContextKey defines a type for keys used to store and retrieve values in a context.
4 | // These keys are used throughout the application to pass and access request-specific data.
5 | type ContextKey string
6 |
7 | const (
8 | ContextKeyIPAddress ContextKey = "ip_address" // Key for storing the client's IP address in the context
9 | ContextKeyRequestID ContextKey = "requestID" // Key for storing the unique request ID in the context
10 | ContextKeySessionID ContextKey = "session_id" // Key for storing the session ID in the context
11 | ContextKeyUserAgent ContextKey = "user_agent" // Key for storing the client's user agent in the context
12 | ContextKeyUserID ContextKey = "user_id" // Key for storing the user ID in the context
13 | ContextKeyTokenClaims ContextKey = "token_claims" // Key for storing token claims in the context
14 | ContextKeyAccessToken ContextKey = "access_token" // Key for storing the access token in the context
15 | )
16 |
--------------------------------------------------------------------------------
/internal/constants/displays.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | DisplayPage string = "page"
5 | DisplayPopup string = "popup"
6 | )
7 |
8 | var ValidAuthenticationDisplays = map[string]bool{
9 | DisplayPage: true,
10 | DisplayPopup: true,
11 | }
12 |
--------------------------------------------------------------------------------
/internal/constants/email.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Email Content Labels
4 | const (
5 | FromAddress string = "From"
6 | EmailSubject string = "Subject"
7 | Recipient string = "To"
8 | HTMLBody string = "text/html"
9 | VerifyEmailAddress string = "Verify Your Email Address"
10 | AccountDeletion string = "Your Account Has Been Deleted"
11 | )
12 |
--------------------------------------------------------------------------------
/internal/constants/env.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Environment Variable Names
4 | const (
5 | CryptoSecretKeyENV string = "CRYPTO_SECRET_KEY"
6 | SMTPFromAddressENV string = "SMTP_FROM_ADDRESS"
7 | SMTPPasswordENV string = "SMTP_PASSWORD"
8 | SMTPUsernameENV string = "SMTP_USERNAME"
9 | TokenIssuerENV string = "TOKEN_ISSUER"
10 | TokenPrivateKeyENV string = "TOKEN_PRIVATE_KEY"
11 | TokenPublicKeyENV string = "TOKEN_PUBLIC_KEY"
12 | ReactBuildPathENV string = "REACT_BUILD_PATH"
13 | VigiloServerModeENV string = "VIGILO_SERVER_MODE"
14 |
15 | EnvFilePath string = "../../.env"
16 | TestEnvFilePath string = "../../../.env.test"
17 | )
18 |
--------------------------------------------------------------------------------
/internal/constants/grant_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Grant types define the methods by which a client can obtain an access token
4 | // in the OAuth 2.0 and OpenID Connect protocols. These constants represent
5 | // the supported grant types in the application.
6 | const (
7 | AuthorizationCodeGrantType string = "authorization_code" // Standard OAuth 2.0 Authorization Code Grant
8 | ClientCredentialsGrantType string = "client_credentials" // Client Credentials Grant
9 | DeviceCodeGrantType string = "urn:ietf:params:oauth:grant-type:device_code" // Device Code Grant
10 | RefreshTokenGrantType string = "refresh_token" // Refresh Token Grant
11 | ImplicitGrantType string = "implicit" // Implicit Flow (deprecated in OAuth 2.1)
12 | PasswordGrantType string = "password" // Resource Owner Password Credentials Grant (deprecated)
13 | )
14 |
15 | // SupportedGrantTypes is a map of grant types supported by the application.
16 | // The key is the grant type, and the value indicates whether it is supported.
17 | var SupportedGrantTypes = map[string]bool{
18 | AuthorizationCodeGrantType: true,
19 | ClientCredentialsGrantType: true,
20 | DeviceCodeGrantType: true,
21 | RefreshTokenGrantType: true,
22 | ImplicitGrantType: true,
23 | PasswordGrantType: true,
24 | }
25 |
--------------------------------------------------------------------------------
/internal/constants/ids.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // ID Prefix Constants define prefixes used for generating unique identifiers
4 | // for various entities in the application.
5 | const (
6 | AuditEventIDPrefix string = "audit-" // Prefix for audit event IDs
7 | SessionIDPrefix string = "sess-" // Prefix for session IDs
8 | RequestIDPrefix string = "req-" // Prefix for request IDs
9 | UserRoleIDPrefix string = "user-" // Prefix for user role IDs
10 | AdminRoleIDPrefix string = "admin-" // Prefix for admin role IDs
11 | ClientIDPrefix string = "client-" // Prefix for client IDs
12 | )
13 |
--------------------------------------------------------------------------------
/internal/constants/oidc.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // OIDC Constants define values used in OpenID Connect (OIDC) for token signing,
4 | // encryption, subject types, and client authentication methods.
5 | const (
6 | SubjectTypePublic string = "public" // Public subject type for OIDC (e.g., non-pairwise identifiers)
7 | SubjectTypePairwise string = "pairwise"
8 | IDTokenSigningAlgorithmRS256 string = "RS256" // Signing algorithm for ID tokens (RSA with SHA-256)
9 | IDTokenEncryptionAlgorithmRSA string = "RSA-OAEP" // Encryption algorithm for ID tokens (RSA-OAEP)
10 | AuthMethodClientSecretPost string = "client_secret_post" // Client authentication using client_id and client_secret in the request body
11 | AuthMethodClientSecretBasic string = "client_secret_basic" // Client authentication using client_id and client_secret in the Authorization header
12 | AuthMethodNone string = "none" // No client authentication (used for public clients, e.g., with PKCE)
13 | )
14 |
--------------------------------------------------------------------------------
/internal/constants/prompts.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | PromptLogin string = "login"
5 | PromptNone string = "none"
6 | PromptConsent string = "consent"
7 | )
8 |
9 | var ValidPrompts = map[string]bool{
10 | PromptLogin: true,
11 | PromptNone: true,
12 | PromptConsent: true,
13 | }
14 |
--------------------------------------------------------------------------------
/internal/constants/response_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Response Types define the types of responses that can be returned
4 | // in OAuth 2.0 and OpenID Connect flows.
5 | const (
6 | CodeResponseType string = "code" // Authorization Code response type
7 | TokenResponseType string = "token" // Implicit Flow token response type
8 | IDTokenResponseType string = "id_token" // ID Token response type for OpenID Connect
9 | HybridResponseType string = "token id_token"
10 | )
11 |
12 | // SupportedResponseTypes is a map of response types supported by the application.
13 | // The key is the response type, and the value indicates whether it is supported.
14 | var SupportedResponseTypes = map[string]bool{
15 | CodeResponseType: true,
16 | TokenResponseType: true,
17 | IDTokenResponseType: true,
18 | HybridResponseType: true,
19 | }
20 |
--------------------------------------------------------------------------------
/internal/constants/roles.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Predefined user roles define the roles that can be assigned to users
4 | // in the application. These roles determine the level of access and permissions.
5 | const (
6 | UserRole string = "USER" // Standard user role with limited access
7 | AdminRole string = "ADMIN" // Administrator role with elevated privileges
8 | )
9 |
10 | // ValidRoles is a map of roles supported by the application.
11 | // The key is the role, and the value indicates whether it is valid.
12 | var ValidRoles = map[string]bool{
13 | UserRole: true,
14 | AdminRole: true,
15 | }
16 |
--------------------------------------------------------------------------------
/internal/constants/timeouts.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | import "time"
4 |
5 | const (
6 | ThreeSecondTimeout time.Duration = 3 * time.Second
7 | FiveSecondTimeout time.Duration = 5 * time.Second
8 | ThirtySecondTimeout time.Duration = 30 * time.Second
9 | TenSecondTimeout time.Duration = 10 * time.Second
10 | )
11 |
--------------------------------------------------------------------------------
/internal/container/lazy_init.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import "sync"
4 |
5 | // LazyInit is a generic type that provides lazy initialization for a value of type T.
6 | // It ensures that the initialization function (initFunc) is executed only once,
7 | // regardless of how many times the value is accessed.
8 | //
9 | // Fields:
10 | // - once: A sync.Once instance used to guarantee that the initialization function
11 | // is executed only once.
12 | // - value: The lazily initialized value of type T.
13 | // - initFunc: A function that initializes and returns the value of type T.
14 | //
15 | // Usage:
16 | // LazyInit can be used to defer the computation or initialization of a value
17 | // until it is actually needed, while ensuring thread-safe access.
18 | type LazyInit[T any] struct {
19 | once sync.Once
20 | value T
21 | initFunc func() T
22 | }
23 |
24 | // Get retrieves the value of the LazyInit instance, initializing it if necessary.
25 | // The initialization is performed only once using the provided initFunc.
26 | // Subsequent calls to Get will return the already initialized value.
27 | //
28 | // Returns:
29 | // - T: The initialized value of the LazyInit instance.
30 | func (l *LazyInit[T]) Get() T {
31 | l.once.Do(func() {
32 | l.value = l.initFunc()
33 | })
34 |
35 | return l.value
36 | }
37 |
--------------------------------------------------------------------------------
/internal/container/server_config_registry.go:
--------------------------------------------------------------------------------
1 | package container
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/vigiloauth/vigilo/v2/idp/config"
9 | )
10 |
11 | type ServerConfigRegistry struct {
12 | tlsConfig *tls.Config
13 | httpServer *http.Server
14 | }
15 |
16 | func NewServerConfigRegistry(services *ServiceRegistry) *ServerConfigRegistry {
17 | sr := &ServerConfigRegistry{}
18 | sr.initServerConfigurations()
19 | return sr
20 | }
21 |
22 | func (sr *ServerConfigRegistry) HTTPServer() *http.Server {
23 | return sr.httpServer
24 | }
25 |
26 | func (sr *ServerConfigRegistry) initServerConfigurations() {
27 | sr.initTLS()
28 | sr.initHTTPServer()
29 | }
30 |
31 | func (sr *ServerConfigRegistry) initTLS() {
32 | sr.tlsConfig = &tls.Config{
33 | MinVersion: tls.VersionTLS12,
34 | CipherSuites: []uint16{
35 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
36 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
37 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
38 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
39 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
40 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
41 | },
42 | }
43 | }
44 |
45 | func (sr *ServerConfigRegistry) initHTTPServer() {
46 | sr.httpServer = &http.Server{
47 | Addr: fmt.Sprintf(":%s", config.GetServerConfig().Port()),
48 | ReadTimeout: config.GetServerConfig().ReadTimeout(),
49 | WriteTimeout: config.GetServerConfig().WriteTimeout(),
50 | TLSConfig: sr.tlsConfig,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/internal/domain/audit/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | // AuditRepository defines the interface for storing and retrieving audit events.
9 | type AuditRepository interface {
10 | // StoreAuditEvent stores an audit event in the repository.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts and cancellations.
14 | // - event *AuditEvent: The audit event to be stored.
15 | //
16 | // Returns:
17 | // - error: An error if storing the event fails, otherwise nil.
18 | StoreAuditEvent(ctx context.Context, event *AuditEvent) error
19 |
20 | // GetAuditEvents retrieves audit events that match the provided filters and time range.
21 | //
22 | // Parameters:
23 | // - ctx Context: The context for managing timeouts and cancellations.
24 | // - filters map[string]any: A map of filter keys and values to apply.
25 | // - from time.Time: The start time of the time range to filter events.
26 | // - to time.Time: The end time of the time range to filter events.
27 | // - limit int: The maximum number of events to return.
28 | // - offset int: The number of events to skip (for pagination).
29 | //
30 | // Returns:
31 | // - []*AuditEvent: A slice of matching audit events.
32 | // - error: An error if the retrieval fails, otherwise nil.
33 | GetAuditEvents(ctx context.Context, filters map[string]any, from time.Time, to time.Time, limit, offset int) ([]*AuditEvent, error)
34 |
35 | // DeleteEvent deletes an event using the given event ID.
36 | //
37 | // Parameters:
38 | // - ctx Context: The context for managing timeouts and cancellations.
39 | // - eventID string: The ID of the event to delete.
40 | //
41 | // Returns:
42 | // - error: An error if deletion fails, otherwise nil.
43 | DeleteEvent(ctx context.Context, eventID string) error
44 | }
45 |
--------------------------------------------------------------------------------
/internal/domain/audit/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | type AuditLogger interface {
9 | // StoreEvent saves an AuditEvent to the repository.
10 | // If an error occurs storing the audit event, no error will be returned so that the flow is not disrupted.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts, cancellations, and for storing/retrieving event metadata.
14 | // - eventType EventType: The type of event to store.
15 | // - success bool: True if the event was successful, otherwise false.
16 | // - action ActionType: The action that is to be audited.
17 | // - method MethodType: The method used (password, email, etc).
18 | // - err error: The error if applicable, otherwise nil.
19 | StoreEvent(ctx context.Context, eventType EventType, success bool, action ActionType, method MethodType, err error)
20 |
21 | // DeleteOldEvents deletes audit events older than the specified timestamp.
22 | //
23 | // Parameters:
24 | // - ctx Context: The context for managing timeouts and cancellations.
25 | // - olderThan time.Time: Events older than this timestamp will be deleted.
26 | //
27 | // Returns:
28 | // - error: An error if deletion fails, otherwise nil.
29 | DeleteOldEvents(ctx context.Context, olderThan time.Time) error
30 |
31 | // GetAuditEvents retrieves audit events that match the provided filters and time range.
32 | //
33 | // Parameters:
34 | // - ctx Context: The context for managing timeouts and cancellations.
35 | // - filters map[string]any: A map of filter keys and values to apply.
36 | // - from string: The start time of the time range to filter events (must be RFC3339 Format).
37 | // - to string: The end time of the time range to filter events (must be RFC3339 Format).
38 | // - limit int: The maximum number of events to return.
39 | // - offset int: The number of events to skip (for pagination).
40 | //
41 | // Returns:
42 | // - []*AuditEvent: A slice of matching audit events.
43 | // - error: An error if the retrieval fails, otherwise nil.
44 | GetAuditEvents(ctx context.Context, filters map[string]any, fromStr string, toStr string, limit, offset int) ([]*AuditEvent, error)
45 | }
46 |
--------------------------------------------------------------------------------
/internal/domain/authorization/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | authz "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
7 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
8 | users "github.com/vigiloauth/vigilo/v2/internal/domain/user"
9 | )
10 |
11 | // AuthorizationService defines the interface for handling client authorization requests.
12 | type AuthorizationService interface {
13 |
14 | // AuthorizeTokenExchange validates the token exchange request for an OAuth 2.0 authorization code grant.
15 | //
16 | // Parameters:
17 | // - ctx Context: The context for managing timeouts and cancellations.
18 | // - tokenRequest token.TokenRequest: The token exchange request containing client and authorization code details.
19 | //
20 | // Returns:
21 | // - *AuthorizationCodeData: The authorization code data if authorization is successful.
22 | // - error: An error if the token exchange request is invalid or fails authorization checks.
23 | AuthorizeTokenExchange(ctx context.Context, tokenRequest *token.TokenRequest) (*authz.AuthorizationCodeData, error)
24 |
25 | // AuthorizeUserInfoRequest validates whether the provided access token claims grant sufficient
26 | // permission to access the /userinfo endpoint.
27 | //
28 | // This method is responsible for performing authorization checks and retrieving the user only. It does not validate the token itself (assumes
29 | // the token has already been validated by the time this method is called).
30 | //
31 | // Parameters:
32 | // - ctx context.Context: The context for managing timeouts and cancellations.
33 | // - claims *TokenClaims: The token claims extracted from the a valid access token. These claims should include the
34 | // 'scope' field, which will be used to verify whether the client is authorized for the request.
35 | //
36 | // Returns:
37 | // - *User: The retrieved user if authorization succeeds, otherwise nil.
38 | // - error: An error if authorization fails, otherwise nil.
39 | AuthorizeUserInfoRequest(ctx context.Context, claims *token.TokenClaims) (*users.User, error)
40 |
41 | // UpdateAuthorizationCode updates the authorization code data in the database.
42 | //
43 | // Parameters:
44 | // - ctx context.Context: The context for managing timeouts and cancellations.
45 | // - authData *AuthorizationCodeData: The authorization code data to update.
46 | //
47 | // Returns:
48 | // - error: An error if the update fails, otherwise nil.
49 | UpdateAuthorizationCode(ctx context.Context, authData *authz.AuthorizationCodeData) error
50 | }
51 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/creator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | type AuthorizationCodeCreator interface {
10 | // GenerateAuthorizationCode creates a new authorization code and stores it with associated data.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts and cancellations.
14 | // - req *ClientAuthorizationRequest: The request containing the metadata to generate an authorization code.
15 | //
16 | // Returns:
17 | // - string: The generated authorization code.
18 | // - error: An error if code generation fails.
19 | GenerateAuthorizationCode(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error)
20 | }
21 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/issuer.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | type AuthorizationCodeIssuer interface {
10 | // IssueAuthorizationCode generates an authorization code for the given client request.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts and cancellations.
14 | // - req *ClientAuthorizationRequest: The request containing the metadata to generate an authorization code.
15 | //
16 | // Returns:
17 | // - string: The generated authorization code.
18 | // - error: An error if code generation fails.
19 | IssueAuthorizationCode(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error)
20 | }
21 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/claims"
8 | "github.com/vigiloauth/vigilo/v2/internal/types"
9 | )
10 |
11 | // AuthorizationCodeData represents the data associated with an authorization code.
12 | type AuthorizationCodeData struct {
13 | UserID string
14 | ClientID string
15 | RedirectURI string
16 | Scope types.Scope
17 | Code string
18 | CodeChallenge string
19 | CodeChallengeMethod types.CodeChallengeMethod
20 | Nonce string
21 | AccessTokenHash string
22 | Used bool
23 | CreatedAt time.Time
24 | UserAuthenticationTime time.Time
25 | Request *http.Request
26 | ClaimsRequest *domain.ClaimsRequest
27 | ACRValues string
28 | }
29 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | type AuthorizationCodeRepository interface {
9 | // StoreAuthorizationCode persists an authorization code with its associated data.
10 | //
11 | // Parameters:
12 | // - ctx Context: The context for managing timeouts and cancellations.
13 | // - code string: The authorization code.
14 | // - data *AuthorizationCodeData: The data associated with the code.
15 | // - expiresAt time.Time: When the code expires.
16 | //
17 | // Returns:
18 | // - error: An error if storing fails, nil otherwise.
19 | StoreAuthorizationCode(ctx context.Context, code string, data *AuthorizationCodeData, expiresAt time.Time) error
20 |
21 | // GetAuthorizationCode retrieves the data associated with an authorization code.
22 | //
23 | // Parameters:
24 | // - ctx Context: The context for managing timeouts and cancellations.
25 | // - code string: The authorization code to look up.
26 | //
27 | // Returns:
28 | // - *AuthorizationCodeData: The associated data if found.
29 | // - error: An error if retrieval fails.
30 | GetAuthorizationCode(ctx context.Context, code string) (*AuthorizationCodeData, error)
31 |
32 | // DeleteAuthorizationCode deletes an authorization code after use.
33 | //
34 | // Parameters:
35 | // - ctx Context: The context for managing timeouts and cancellations.
36 | // - code string: The authorization code to remove.
37 | //
38 | // Returns:
39 | // - error: An error if removal fails, nil otherwise.
40 | DeleteAuthorizationCode(ctx context.Context, code string) error
41 |
42 | // UpdateAuthorizationCode updates existing authorization code data.
43 | //
44 | // Parameters:
45 | // - ctx Context: The context for managing timeouts and cancellations.
46 | // - code string: The authorization code to update.
47 | // - authData *AuthorizationCodeData: The update authorization code data.
48 | //
49 | // Returns:
50 | // - error: An error if update fails, nil otherwise.
51 | UpdateAuthorizationCode(ctx context.Context, code string, authData *AuthorizationCodeData) error
52 | }
53 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type AuthorizationCodeManager interface {
8 | // RevokeAuthorizationCode explicitly invalidates a code.
9 | //
10 | // Parameters:
11 | // - ctx Context: The context for managing timeouts and cancellations.
12 | // - code string: The authorization code to revoke.
13 | //
14 | // Returns:
15 | // - error: An error if revocation fails.
16 | RevokeAuthorizationCode(ctx context.Context, code string) error
17 |
18 | // GetAuthorizationCode retrieves the authorization code data for a given code.
19 | //
20 | // Parameters:
21 | // - ctx Context: The context for managing timeouts and cancellations.
22 | // - code string: The authorization code to retrieve.
23 | //
24 | // Returns:
25 | // - *AuthorizationCodeData: The authorization code data if found, or nil if no matching code exists.
26 | GetAuthorizationCode(ctx context.Context, code string) (*AuthorizationCodeData, error)
27 |
28 | // UpdateAuthorizationCode updates the provided authorization code data in the repository.
29 | //
30 | // Parameters:
31 | // - ctx Context: The context for managing timeouts and cancellations.
32 | // - authData (*authz.AuthorizationCodeData): The authorization code data to be updated.
33 | //
34 | // Returns:
35 | // - error: An error if updated the authorization code fails, or nil if the operation succeeds.
36 | UpdateAuthorizationCode(ctx context.Context, authData *AuthorizationCodeData) error
37 | }
38 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/validation.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "github.com/vigiloauth/vigilo/v2/internal/errors"
4 |
5 | func (c *AuthorizationCodeData) ValidateFields(clientID, redirectURI string) error {
6 | if c.Used {
7 | return errors.New(errors.ErrCodeInvalidGrant, "authorization code has already been used")
8 | } else if c.ClientID != clientID {
9 | return errors.New(errors.ErrCodeInvalidGrant, "authorization code client ID and request client ID do no match")
10 | } else if c.RedirectURI != redirectURI {
11 | return errors.New(errors.ErrCodeInvalidGrant, "authorization code redirect URI and request redirect URI do no match")
12 | }
13 |
14 | return nil
15 | }
16 |
--------------------------------------------------------------------------------
/internal/domain/authzcode/validator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | type AuthorizationCodeValidator interface {
10 | // ValidateRequest checks the validity of the client authorization request.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts and cancellations.
14 | // - req *ClientAuthorizationRequest: The request to validate.
15 | //
16 | // Returns:
17 | // - error: An error if the request is invalid.
18 | ValidateRequest(ctx context.Context, req *client.ClientAuthorizationRequest) error
19 |
20 | // ValidateAuthorizationCode checks if a code is valid and returns the associated data.
21 | //
22 | // Parameters:
23 | // - ctx Context: The context for managing timeouts and cancellations.
24 | // - code string: The authorization code to validate.
25 | // - clientID string: The client requesting validation.
26 | // - redirectURI string: The redirect URI to verify.
27 | //
28 | // Returns:
29 | // - error: An error if validation fails.
30 | ValidateAuthorizationCode(ctx context.Context, code, clientID, redirectURI string) error
31 |
32 | // ValidatePKCE validates the PKCE (Proof Key for Code Exchange) parameters during the token exchange process.
33 | //
34 | // This method checks if the provided code verifier matches the code challenge stored in the authorization code data.
35 | // It supports the "S256" (SHA-256) and "plain" code challenge methods.
36 | //
37 | // Parameters:
38 | // - ctx Context: The context for managing timeouts and cancellations.
39 | // - authzCodeData (*authz.AuthorizationCodeData): The authorization code data containing the code challenge and method.
40 | // - codeVerifier (string): The code verifier provided by the client during the token exchange.
41 | //
42 | // Returns:
43 | // - error: An error if the validation fails, including cases where the code verifier does not match the code challenge
44 | // or if the code challenge method is unsupported. Returns nil if validation succeeds.
45 | ValidatePKCE(ctx context.Context, authzCodeData *AuthorizationCodeData, codeVerifier string) error
46 | }
47 |
--------------------------------------------------------------------------------
/internal/domain/claims/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "encoding/json"
5 | "net/url"
6 | )
7 |
8 | type ClaimsRequest struct {
9 | UserInfo *ClaimSet `json:"userinfo,omitempty"`
10 | }
11 |
12 | type ClaimSet map[string]*ClaimSpec
13 |
14 | type ClaimSpec struct {
15 | Essential bool `json:"essential,omitempty"`
16 | Value string `json:"value,omitempty"`
17 | }
18 |
19 | func ParseClaimsParameter(claimsParam string) (*ClaimsRequest, error) {
20 | decodedClaims, _ := url.QueryUnescape(claimsParam)
21 | var claimsRequest ClaimsRequest
22 | _ = json.Unmarshal([]byte(decodedClaims), &claimsRequest)
23 | return &claimsRequest, nil
24 | }
25 |
26 | func SerializeClaims(claims *ClaimsRequest) string {
27 | if claims != nil {
28 | claimsJSON, err := json.Marshal(claims)
29 | if err != nil {
30 | claimsJSON = nil
31 | }
32 | return string(claimsJSON)
33 | }
34 |
35 | return ""
36 | }
37 |
--------------------------------------------------------------------------------
/internal/domain/client/authenticator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/vigiloauth/vigilo/v2/internal/types"
8 | )
9 |
10 | // ClientAuthenticator defines an interface for authenticating HTTP client requests.
11 | type ClientAuthenticator interface {
12 | // AuthenticateRequest validates the incoming HTTP request to ensure the client has the required scope.
13 | //
14 | // Parameters:
15 | // - ctx context.Context: The context for managing timeouts and cancellations.
16 | // - r *http.Request: The HTTP request to authenticate.
17 | // - requiredScope types.Scope: The scope required to access the requested resource.
18 | //
19 | // Returns:
20 | // - error: An error if authentication fails or the required scope is not met.
21 | AuthenticateRequest(ctx context.Context, r *http.Request, requiredScope types.Scope) error
22 |
23 | // AuthenticateClient authenticates the client using provided credentials
24 | // and authorizes access by validating required grant types and scopes.
25 | //
26 | // Parameters:
27 | // - ctx Context: The context for managing timeouts and cancellations.
28 | // - req *ClientAuthenticationRequest: The request containing client credentials and required scopes.
29 | //
30 | // Returns:
31 | // - error: An error if authentication or authorization fails.
32 | AuthenticateClient(ctx context.Context, req *ClientAuthenticationRequest) error
33 | }
34 |
--------------------------------------------------------------------------------
/internal/domain/client/authorization.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type ClientAuthorization interface {
6 | // Authorize handles the authorization logic for a client request.
7 | //
8 | // Parameters:
9 | // - ctx Context: The context for managing timeouts and cancellations.
10 | // - authorizationRequest *ClientAuthorizationRequest: The client authorization request.
11 | //
12 | // Returns:
13 | // - string: The redirect URL, or an empty string if authorization failed.
14 | // - error: An error message, if any.
15 | //
16 | // Errors:
17 | // - Returns an error message if the user is not authenticated, consent is denied, or authorization code generation fails.
18 | Authorize(ctx context.Context, request *ClientAuthorizationRequest) (string, error)
19 | }
20 |
--------------------------------------------------------------------------------
/internal/domain/client/creator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type ClientCreator interface {
6 | Register(ctx context.Context, req *ClientRegistrationRequest) (*ClientRegistrationResponse, error)
7 | }
8 |
--------------------------------------------------------------------------------
/internal/domain/client/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | // ClientRepository defines the interface for storing and managing Clients.
6 | type ClientRepository interface {
7 | // SaveClient adds a new client to the store if it does not already exist.
8 | //
9 | // Parameters:
10 | // - ctx Context: The context for managing timeouts and cancellations.
11 | // - client *Client: The client object to store.
12 | //
13 | // Returns:
14 | // - error: An error if the client already exists, nil otherwise.
15 | SaveClient(ctx context.Context, client *Client) error
16 |
17 | // GetClientByID retrieves a client by its ID.
18 | //
19 | // Parameters:
20 | // - ctx Context: The context for managing timeouts and cancellations.
21 | // - clientID string: The ID of the client to retrieve.
22 | //
23 | // Returns:
24 | // - *Client: The client object if found, nil otherwise.
25 | // - error: An error if retrieval fails.
26 | GetClientByID(ctx context.Context, clientID string) (*Client, error)
27 |
28 | // DeleteClient removes a client from the store by its ID.
29 | //
30 | // Parameters:
31 | // - ctx Context: The context for managing timeouts and cancellations.
32 | // - clientID string: The ID of the client to delete.
33 | //
34 | // Returns:
35 | // - error: Returns an error if deletion fails, otherwise false.
36 | DeleteClientByID(ctx context.Context, clientID string) error
37 |
38 | // UpdateClient updates an existing client in the store.
39 | //
40 | // Parameters:
41 | // - ctx Context: The context for managing timeouts and cancellations.
42 | // - client *Client: The updated client object.
43 | //
44 | // Returns:
45 | // - error: An error if the client does not exist, nil otherwise.
46 | UpdateClient(ctx context.Context, client *Client) error
47 |
48 | // IsExistingID checks to see if an ID already exists in the database.
49 | //
50 | // Parameters:
51 | // - ctx Context: The context for managing timeouts and cancellations.
52 | // - clientID string: The client ID to verify.
53 | //
54 | // Returns:
55 | // - bool: True if it exists, otherwise false.
56 | IsExistingID(ctx context.Context, clientID string) bool
57 | }
58 |
--------------------------------------------------------------------------------
/internal/domain/client/validator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type ClientValidator interface {
8 | ValidateRegistrationRequest(ctx context.Context, req *ClientRegistrationRequest) error
9 | ValidateUpdateRequest(ctx context.Context, req *ClientUpdateRequest) error
10 | ValidateAuthorizationRequest(ctx context.Context, req *ClientAuthorizationRequest) error
11 | ValidateRedirectURI(ctx context.Context, redirectURI string, existingClient *Client) error
12 | ValidateClientAndRegistrationAccessToken(ctx context.Context, clientID string, registrationAccessToken string) error
13 | }
14 |
--------------------------------------------------------------------------------
/internal/domain/cookies/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "time"
7 | )
8 |
9 | type HTTPCookieService interface {
10 | // SetSessionCookie sets the session token in an HttpOnly cookie.
11 | // It also sets the cookie's expiration time and other attributes.
12 | //
13 | // Parameters:
14 | // - ctx Context: The context for managing timeouts and cancellations.
15 | // - w http.ResponseWriter: The HTTP response writer.
16 | // - sessionID string: The session ID to set in the cookie.
17 | // - expirationTime time.Duration: The expiration time for the cookie.
18 | SetSessionCookie(ctx context.Context, w http.ResponseWriter, sessionID string, expirationTime time.Duration)
19 |
20 | // ClearSessionCookie clears the session token cookie.
21 | //
22 | // Parameters:
23 | // - ctx Context: The context for managing timeouts and cancellations.
24 | // - w http.ResponseWriter: The HTTP response writer.
25 | ClearSessionCookie(ctx context.Context, w http.ResponseWriter)
26 |
27 | // GetSessionToken retrieves the session cookie from the request.
28 | //
29 | // Parameters:
30 | // - r *http.Request: The HTTP request containing the session.
31 | //
32 | // Returns:
33 | // - string: The session cookie if found, otherwise nil.
34 | // - error: An error if retrieving the cookie fails.
35 | GetSessionCookie(r *http.Request) (*http.Cookie, error)
36 | }
37 |
--------------------------------------------------------------------------------
/internal/domain/email/mailer.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "gopkg.in/gomail.v2"
4 |
5 | // Mailer defines methods for interacting with an email service using gomail.
6 | // It includes functionality for dialing, sending emails, and creating email messages.
7 | type Mailer interface {
8 | // Dial establishes a connection to the email server with the provided host, port,
9 | // username, and password. It returns a `gomail.SendCloser` which can be used
10 | // to send emails, or an error if the connection fails.
11 | //
12 | // Parameters:
13 | // - host string: The email server's host.
14 | // - port string: The port number to connect to on the email server.
15 | // - username string: The username for authenticating to the email server.
16 | // - password string: The password for authenticating to the email server.
17 | //
18 | // Returns:
19 | // - gomail.SendCloser: A send closer to send emails.
20 | // - error: An error if the connection fails, or nil if successful.
21 | Dial(host string, port int, username string, password string) (gomail.SendCloser, error)
22 |
23 | // DialAndSend connects to the email server and immediately sends the provided email message(s).
24 | // It returns an error if the connection or sending process fails.
25 | //
26 | // Parameters:
27 | // - host string: The email server's host.
28 | // - port string: The port number to connect to on the email server.
29 | // - username string: The username for authenticating to the email server.
30 | // - password string: The password for authenticating to the email server.
31 | // - message string: A list of gomail messages to send. One or more messages can be provided.
32 | //
33 | // Returns:
34 | // - error: An error indicating the failure to send the email(s), or nil if successful.
35 | DialAndSend(host string, port int, username string, password string, message ...*gomail.Message) error
36 |
37 | // NewMessage creates a new gomail message using the provided email request, body,
38 | // subject, and sender address. This message can then be sent using the DialAndSend method.
39 | //
40 | // Parameters:
41 | // - request string: The request object containing email details.
42 | // - body string: The body content of the email.
43 | // - subject string: The subject of the email.
44 | // - fromAddress string: The sender's email address.
45 | //
46 | // Returns:
47 | // - *gomail.Message: A new gomail message containing the provided email details.
48 | NewMessage(request *EmailRequest, body string, subject string, fromAddress string) *gomail.Message
49 | }
50 |
--------------------------------------------------------------------------------
/internal/domain/email/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/vigiloauth/vigilo/v2/internal/utils"
7 | )
8 |
9 | type EmailRequest struct {
10 | Recipient string
11 | EmailType EmailType
12 | VerificationCode string
13 | VerificationToken string
14 | BaseURL string
15 | ID string
16 | Retries int
17 | }
18 |
19 | func NewEmailRequest(recipient, verificationCode, verificationToken string, emailType EmailType) *EmailRequest {
20 | return &EmailRequest{
21 | Recipient: recipient,
22 | VerificationCode: verificationCode,
23 | VerificationToken: verificationCode,
24 | EmailType: emailType,
25 | ID: utils.GenerateUUID(),
26 | Retries: 0,
27 | }
28 | }
29 |
30 | type EmailType string
31 |
32 | const (
33 | AccountVerification EmailType = "account_verification"
34 | AccountDeletion EmailType = "account_deletion"
35 | )
36 |
37 | func (t EmailType) String() string {
38 | return string(t)
39 | }
40 |
41 | type EmailRetryQueue struct {
42 | mu sync.Mutex
43 | requests []*EmailRequest
44 | }
45 |
46 | func (q *EmailRetryQueue) Add(request *EmailRequest) {
47 | q.mu.Lock()
48 | defer q.mu.Unlock()
49 | q.requests = append(q.requests, request)
50 | }
51 |
52 | func (q *EmailRetryQueue) Remove() *EmailRequest {
53 | q.mu.Lock()
54 | defer q.mu.Unlock()
55 |
56 | if len(q.requests) == 0 {
57 | return nil
58 | }
59 |
60 | request := q.requests[0]
61 | q.requests = q.requests[1:]
62 | return request
63 | }
64 |
65 | func (q *EmailRetryQueue) IsEmpty() bool {
66 | q.mu.Lock()
67 | defer q.mu.Unlock()
68 | return len(q.requests) == 0
69 | }
70 |
71 | func (q *EmailRetryQueue) Size() int {
72 | q.mu.Lock()
73 | defer q.mu.Unlock()
74 | return len(q.requests)
75 | }
76 |
--------------------------------------------------------------------------------
/internal/domain/email/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | // EmailService defines methods for sending emails, testing connections,
6 | // and managing email retry queues.
7 | type EmailService interface {
8 | // SendEmail sends an email based on the provided request.
9 | // It returns an error if the email could not be sent.
10 | //
11 | // Parameters:
12 | // - ctx Context: The context for managing timeouts and cancellations.
13 | // - request *EmailRequest: The email request containing necessary details for sending the email.
14 | //
15 | // Returns:
16 | // - error: An error indicating the failure to send the email, or nil if successful.
17 | SendEmail(ctx context.Context, request *EmailRequest) error
18 |
19 | // TestConnection tests the connection to the email service.
20 | // It returns an error if the connection test fails.
21 | //
22 | // Returns:
23 | // - error: An error indicating the failure of the connection test, or nil if successful.
24 | TestConnection() error
25 |
26 | // GetEmailRetryQueue retrieves the current email retry queue.
27 | // The retry queue contains emails that failed to send and are awaiting retry.
28 | //
29 | // Returns:
30 | // - *EmailRetryQueue: The current retry queue. If there are no failed emails, returns an empty queue.
31 | GetEmailRetryQueue() *EmailRetryQueue
32 | }
33 |
--------------------------------------------------------------------------------
/internal/domain/jwks/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "crypto/rsa"
5 | "encoding/base64"
6 | "math/big"
7 | )
8 |
9 | type Jwks struct {
10 | Keys []JWK `json:"keys"`
11 | }
12 |
13 | type JWK struct {
14 | Kty string `json:"kty"` // Key type (e.g., "RSA", "EC")
15 | Kid string `json:"kid"` // Key ID, used to identify the key
16 | Use string `json:"use"` // Public key use (e.g., "sig" for signature, "enc" for encryption)
17 | Alg string `json:"alg"` // Algorithm intended for use with the key (e.g., "RS256", "ES256")
18 | N string `json:"n,omitempty"` // RSA modulus (base64url-encoded)
19 | E string `json:"e,omitempty"` // RSA public exponent (base64url-encoded)
20 | X string `json:"x,omitempty"` // EC public key x-coordinate (base64url-encoded)
21 | Y string `json:"y,omitempty"` // EC public key y-coordinate (base64url-encoded)
22 | Crv string `json:"crv,omitempty"` // EC curve name (e.g., "P-256", "P-384", "P-521")
23 | }
24 |
25 | func NewJWK(keyID string, publicKey *rsa.PublicKey) JWK {
26 | return JWK{
27 | Kty: "RSA",
28 | Kid: keyID,
29 | Use: "sig",
30 | Alg: "RS256",
31 | N: base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes()),
32 | E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()),
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/internal/domain/jwt/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
7 | )
8 |
9 | type JWTService interface {
10 | ParseWithClaims(ctx context.Context, tokenString string) (*token.TokenClaims, error)
11 | SignToken(ctx context.Context, claims *token.TokenClaims) (string, error)
12 | }
13 |
--------------------------------------------------------------------------------
/internal/domain/login/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | // LoginAttemptRepository defines the interface for storing and retrieving login attempts.
10 | type LoginAttemptRepository interface {
11 |
12 | // SaveLoginAttempt saves a login attempt.
13 | //
14 | // Parameters:
15 | // - ctx Context: The context for managing timeouts and cancellations.
16 | // - attempt *UserLoginAttempt: The login attempt to save.
17 | //
18 | // Returns:
19 | // - error: If an error occurs saving the login attempts.
20 | SaveLoginAttempt(ctx context.Context, attempt *user.UserLoginAttempt) error
21 |
22 | // GetLoginAttemptsByUserID retrieves all login attempts for a given user.
23 | //
24 | // Parameters:
25 | // - ctx Context: The context for managing timeouts and cancellations.
26 | // - userID string: The user ID.
27 | //
28 | // Returns:
29 | // - []*UserLoginAttempt: A slice of login attempts for the user.
30 | // - error: If an error occurs retrieving user login attempts.
31 | GetLoginAttemptsByUserID(ctx context.Context, userID string) ([]*user.UserLoginAttempt, error)
32 | }
33 |
--------------------------------------------------------------------------------
/internal/domain/login/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | type LoginAttemptService interface {
10 | // SaveLoginAttempt logs a login attempt.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts and cancellations.
14 | // - attempt *UserLoginAttempt: The login attempt to save.
15 | SaveLoginAttempt(ctx context.Context, attempt *domain.UserLoginAttempt) error
16 |
17 | // GetLoginAttemptsByUserID retrieves all login attempts for a given user.
18 | //
19 | // Parameters:
20 | // - ctx Context: The context for managing timeouts and cancellations.
21 | // - userID string: The user ID.
22 | //
23 | // Returns:
24 | // - []*UserLoginAttempt: A slice of login attempts for the user.
25 | // - error: An error if retrieval fails.
26 | GetLoginAttemptsByUserID(ctx context.Context, userID string) ([]*domain.UserLoginAttempt, error)
27 |
28 | // HandleFailedLoginAttempt handles a failed login attempt.
29 | // It updates the user's last failed login time, saves the login attempt, and locks the account if necessary.
30 | //
31 | // Parameters:
32 | // - ctx Context: The context for managing timeouts and cancellations.
33 | // - user *User: The user who attempted to log in.
34 | // - attempt *UserLoginAttempt: The login attempt information.
35 | //
36 | // Returns:
37 | // - error: An error if an operation fails.
38 | HandleFailedLoginAttempt(ctx context.Context, user *domain.User, attempt *domain.UserLoginAttempt) error
39 | }
40 |
--------------------------------------------------------------------------------
/internal/domain/oidc/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | jwks "github.com/vigiloauth/vigilo/v2/internal/domain/jwks"
7 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
8 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
9 | )
10 |
11 | type OIDCService interface {
12 | // GetUserInfo retrieves the user's profile information based on the claims
13 | // extracted from a validated access token.
14 | //
15 | // Parameters:
16 | // - ctx Context: The context for managing timeouts and cancellations.
17 | // - accessTokenClaims *TokenClaims: A pointer to TokenClaims that were parsed and validated
18 | // from the access token. These typically include standard OIDC claims such as
19 | // 'sub' (subject identifier), 'scope', 'exp' (expiration), etc.
20 | //
21 | // Returns:
22 | // - *UserInfoResponse: A pointer to a UserInfoResponse struct containing the requested user
23 | // information (e.g., name, email, profile picture), filtered according to the
24 | // authorized scopes.
25 | // - error: An error if the user cannot be found, the scopes are insufficient, or any
26 | // other issue occurs during retrieval.
27 | GetUserInfo(ctx context.Context, accessTokenClaims *token.TokenClaims) (*user.UserInfoResponse, error)
28 |
29 | // GetJwks retrieves the JSON Web Key Set (JWKS) used for verifying signatures
30 | // of tokens issued by the OpenID Connect provider.
31 | //
32 | // Parameters:
33 | // - ctx Context: The context for managing timeouts and cancellations.
34 | //
35 | // Returns:
36 | // - *Jwks: A pointer to a Jwks struct containing the public keys in JWKS format.
37 | GetJwks(ctx context.Context) *jwks.Jwks
38 | }
39 |
--------------------------------------------------------------------------------
/internal/domain/session/manager.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | type SessionManager interface {
9 | // GetUserIDFromSession checks if the user session is active based on the provided context and HTTP request.
10 | //
11 | // Parameters:
12 | // - ctx Context: The context for managing timeouts and cancellations.
13 | // - r *http.Request: The HTTP request associated with the user session.
14 | //
15 | // Returns:
16 | // - string: The user ID if the session is active, or an empty string if not.
17 | // - error: An error if the session data retrieval fails.
18 | GetUserIDFromSession(ctx context.Context, r *http.Request) (string, error)
19 |
20 | // GetUserAuthenticationTime retrieves the authentication time of the user session based on the provided context and HTTP request.
21 | //
22 | // Parameters:
23 | // - ctx Context: The context for managing timeouts and cancellations.
24 | // - r *http.Request: The HTTP request associated with the user session.
25 | //
26 | // Returns:
27 | // - int64: The authentication time in Unix timestamp format.
28 | // - error: An error if the session data retrieval fails.
29 | GetUserAuthenticationTime(ctx context.Context, r *http.Request) (int64, error)
30 | }
31 |
--------------------------------------------------------------------------------
/internal/domain/session/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "time"
4 |
5 | // SessionData represents the data stored for a user's session.
6 | type SessionData struct {
7 | ID string // ID of the session.
8 | UserID string // ID of the user associated with the session.
9 | State string // Random string used to prevent CSRF attacks during Authorization Flow.
10 | ClientID string // ID of the OAuth 2.0 client application.
11 | ClientName string // Name of the OAuth 2.0 client application.
12 | IPAddress string // IP address of the user at the time of session creation.
13 | UserAgent string // User agent string of the user's browser or device.
14 | RedirectURI string // Redirect URI from OAuth 2.0 client application.
15 | ExpirationTime time.Time // The timestamp when the session expires.
16 | AuthenticationTime int64 // The timestamp of when the user was last authenticated
17 | }
18 |
--------------------------------------------------------------------------------
/internal/domain/session/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // SessionRepository defines the interface for session management operations.
8 | type SessionRepository interface {
9 | // SaveSession creates a new session and returns the session ID.
10 | //
11 | // Parameters:
12 | // - ctx Context: The context for managing timeouts and cancellations.
13 | // - sessionData SessionData: The data to store in the new session.
14 | //
15 | // Returns:
16 | // - error: An error if the session creation fails.
17 | SaveSession(ctx context.Context, sessionData *SessionData) error
18 |
19 | // GetSessionByID retrieves session data for a given session ID.
20 | //
21 | // Parameters:
22 | // - ctx Context: The context for managing timeouts and cancellations.
23 | // - sessionID string: The unique identifier of the session to retrieve.
24 | //
25 | // Returns:
26 | // - *SessionData: The session data associated with the session ID.
27 | // - error: An error if the session is not found or retrieval fails.
28 | GetSessionByID(ctx context.Context, sessionID string) (*SessionData, error)
29 |
30 | // UpdateSessionByID updates the session data for a given session ID.
31 | //
32 | // Parameters:
33 | // - ctx Context: The context for managing timeouts and cancellations.
34 | // - sessionID string: The unique identifier of the session to update.
35 | // - sessionData SessionData: The updated session data.
36 | //
37 | // Returns:
38 | // - error: An error if the update fails.
39 | UpdateSessionByID(ctx context.Context, sessionID string, sessionData *SessionData) error
40 |
41 | // DeleteSessionByID removes a session with the given session ID.
42 | //
43 | // Parameters:
44 | // - ctx Context: The context for managing timeouts and cancellations.
45 | // - sessionID string: The unique identifier of the session to delete.
46 | //
47 | // Returns:
48 | // - error: An error if the deletion fails.
49 | DeleteSessionByID(ctx context.Context, sessionID string) error
50 | }
51 |
--------------------------------------------------------------------------------
/internal/domain/session/service.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // SessionService defines the interface for session management.
8 | type SessionService interface {
9 | // CreateSession creates a new session token and sets it in an HttpOnly cookie.
10 | //
11 | // Parameters:
12 | // - w http.ResponseWriter: The HTTP response writer.
13 | // - r *http.Request: The HTTP request.
14 | // - userID string: The user's ID address.
15 | // - sessionExpiration time.Duration: The session expiration time.
16 | //
17 | // Returns:
18 | // - error: An error if token generation or cookie setting fails.
19 | CreateSession(w http.ResponseWriter, r *http.Request, sessionData *SessionData) error
20 |
21 | // InvalidateSession invalidates the session token by adding it to the blacklist.
22 | //
23 | // Parameters:
24 | // - w http.ResponseWriter: The HTTP response writer.
25 | // - r *http.Request: The HTTP request.
26 | //
27 | // Returns:
28 | // - error: An error if token parsing or blacklist addition fails.
29 | InvalidateSession(w http.ResponseWriter, r *http.Request) error
30 |
31 | // GetUserIDFromSession retrieves the user ID from the current session.
32 | //
33 | // Parameters:
34 | // - r *http.Request: The HTTP request.
35 | //
36 | // Returns:
37 | // - string: The user ID.
38 | // - error: If an error occurs retrieving the user ID from the session.
39 | GetUserIDFromSession(r *http.Request) (string, error)
40 |
41 | // UpdateSession updates the current session.
42 | //
43 | // Parameters:
44 | // - r *http.Request: The HTTP request.
45 | // - sessionData *SessionData: The sessionData to update.
46 | //
47 | // Returns:
48 | // - error: If an error occurs during the update.
49 | UpdateSession(r *http.Request, sessionData *SessionData) error
50 |
51 | // GetSessionData retrieves the current session.
52 | //
53 | // Parameters:
54 | // - r *http.Request: The HTTP request.
55 | //
56 | // Returns:
57 | // - *SessionData: The session data is successful.
58 | // - error: An error if retrieval fails.
59 | GetSessionData(r *http.Request) (*SessionData, error)
60 | }
61 |
--------------------------------------------------------------------------------
/internal/domain/token/issuer.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/claims"
8 | "github.com/vigiloauth/vigilo/v2/internal/types"
9 | )
10 |
11 | type TokenIssuer interface {
12 | IssueAccessToken(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error)
13 | IssueTokenPair(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string, claims *domain.ClaimsRequest) (string, string, error)
14 | IssueIDToken(ctx context.Context, subject string, audience string, scopes types.Scope, nonce string, acrValues string, authTime time.Time) (string, error)
15 | }
16 |
--------------------------------------------------------------------------------
/internal/domain/token/parser.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type TokenParser interface {
6 | // ParseToken parses a JWT token string into TokenClaims.
7 | //
8 | // Parameters:
9 | // - ctx ctx.Context: Context for the request, containing the request ID for logging.
10 | // - tokenString string: The JWT token string to parse and validate.
11 | //
12 | // Returns:
13 | // - *token.TokenClaims: The parsed token claims if successful.
14 | // - error: An error if token parsing, decryption, or validation fails.
15 | ParseToken(ctx context.Context, tokenString string) (*TokenClaims, error)
16 | }
17 |
--------------------------------------------------------------------------------
/internal/domain/token/validation.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 |
7 | "github.com/vigiloauth/vigilo/v2/internal/errors"
8 | )
9 |
10 | func (t *TokenRequest) ValidateCodeVerifier() error {
11 | codeVerifierLength := len(t.CodeVerifier)
12 | if codeVerifierLength < 43 || codeVerifierLength > 128 {
13 | return errors.New(errors.ErrCodeInvalidRequest, fmt.Sprintf("invalid code verifier length (%d): must be between 43 and 128 characters", codeVerifierLength))
14 | }
15 |
16 | validCodeVerifierRegex := regexp.MustCompile(`^[A-Za-z0-9._~-]+$`)
17 | if !validCodeVerifierRegex.MatchString(t.CodeVerifier) {
18 | return errors.New(errors.ErrCodeInvalidRequest, "invalid characters: only A-Z, a-z, 0-9, '-', and '_' are allowed (Base64 URL encoding)")
19 | }
20 |
21 | return nil
22 | }
23 |
--------------------------------------------------------------------------------
/internal/domain/token/validator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type TokenValidator interface {
6 |
7 | // ValidateToken checks to see if a token is blacklisted or expired.
8 | //
9 | // Parameters:
10 | // - ctx Context: The context for managing timeouts and cancellations.
11 | // - tokenStr string: The token string to check.
12 | //
13 | // Returns:
14 | // - error: An error if the token is blacklisted or expired.
15 | ValidateToken(ctx context.Context, tokenStr string) error
16 | }
17 |
--------------------------------------------------------------------------------
/internal/domain/user/authenticator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type UserAuthenticator interface {
6 | // AuthenticateUserWithRequest authenticates a user based on a login request and request metadata.
7 | //
8 | // This method constructs a User object and a UserLoginAttempt object from the provided
9 | // login request and HTTP request metadata, then delegates the authentication process
10 | // to the AuthenticateUser method.
11 | //
12 | // Parameters:
13 | // - ctx Context: The context for managing timeouts, cancellations, and for retrieving/storing request metadata.
14 | // - request *UserLoginRequest: The login request containing the user's email and password.
15 | //
16 | // Returns:
17 | // - *UserLoginResponse: The response containing user information and a JWT token if authentication is successful.
18 | // - error: An error if authentication fails or if the input is invalid.
19 | AuthenticateUser(ctx context.Context, request *UserLoginRequest) (*UserLoginResponse, error)
20 | }
21 |
--------------------------------------------------------------------------------
/internal/domain/user/creator.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type UserCreator interface {
6 | // CreateUser creates a new user in the system.
7 | //
8 | // Parameters:
9 | // - ctx Context: The context for managing timeouts and cancellations.
10 | // - user *User: The user to register.
11 | //
12 | // Returns:
13 | // - *UserRegistrationResponse: The registered user object and an access token.
14 | // - error: An error if any occurred during the process.
15 | CreateUser(ctx context.Context, user *User) (*UserRegistrationResponse, error)
16 | }
17 |
--------------------------------------------------------------------------------
/internal/domain/user/manager.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type UserManager interface {
6 | // GetUserByUsername retrieves a user using their username.
7 | //
8 | // Parameter:
9 | // - ctx Context: The context for managing timeouts and cancellations.
10 | // - username string: The username of the user to retrieve.
11 | //
12 | // Returns:
13 | // - *User: The retrieved user, otherwise nil.
14 | // - error: If an error occurs retrieving the user.
15 | GetUserByUsername(ctx context.Context, username string) (*User, error)
16 |
17 | // GetUserByID retrieves a user from the store using their ID.
18 | //
19 | // Parameters:
20 | // - ctx Context: The context for managing timeouts and cancellations.
21 | // - userID string: The ID used to retrieve the user.
22 | //
23 | // Returns:
24 | // - *User: The User object if found, or nil if not found.
25 | // - error: If an error occurs retrieving the user.
26 | GetUserByID(ctx context.Context, userID string) (*User, error)
27 |
28 | // DeleteUnverifiedUsers deletes any user that hasn't verified their account and
29 | // has been created for over a week.
30 | //
31 | // Parameter:
32 | // - ctx Context: The context for managing timeouts and cancellations.
33 | //
34 | // Returns:
35 | // - error: an error if deletion fails, otherwise nil.
36 | DeleteUnverifiedUsers(ctx context.Context) error
37 |
38 | // ResetPassword resets the user's password using the provided reset token.
39 | //
40 | // Parameters:
41 | // - ctx Context: The context for managing timeouts and cancellations.
42 | // - userEmail string: The user's email address.
43 | // - newPassword string: The new password.
44 | // - resetToken string: The reset token.
45 | //
46 | // Returns:
47 | // - *users.UserPasswordResetResponse: A response message.
48 | // - error: An error if the operation fails.
49 | ResetPassword(ctx context.Context, userEmail, newPassword, resetToken string) (*UserPasswordResetResponse, error)
50 | }
51 |
--------------------------------------------------------------------------------
/internal/domain/user/verifier.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import "context"
4 |
5 | type UserVerifier interface {
6 | // VerifyEmailAddress validates the verification code and marks the user's email as verified.
7 | //
8 | // Parameter:
9 | // - ctx Context: The context for managing timeouts and cancellations.
10 | // - verificationCode string: The verification code to verify.
11 | //
12 | // Returns:
13 | // - error: an error if validation fails, otherwise nil.
14 | VerifyEmailAddress(ctx context.Context, verificationCode string) error
15 | }
16 |
--------------------------------------------------------------------------------
/internal/domain/userconsent/model.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/vigiloauth/vigilo/v2/internal/types"
7 | )
8 |
9 | // UserConsentRecord represents a user's consent for a specific client
10 | type UserConsentRecord struct {
11 | UserID string
12 | ClientID string
13 | Scope types.Scope
14 | CreatedAt time.Time
15 | }
16 |
17 | type UserConsentResponse struct {
18 | Approved bool `json:"approved"`
19 | ClientID string `json:"client_id"`
20 | ClientName string `json:"client_name"`
21 | RedirectURI string `json:"redirect_uri"`
22 | Scopes []types.Scope `json:"scopes"`
23 | ConsentEndpoint string `json:"consent_endpoint"`
24 | State string `json:"state"`
25 | Error string `json:"error,omitempty"`
26 | Success bool `json:"success,omitempty"`
27 | }
28 |
29 | type UserConsentRequest struct {
30 | Approved bool `json:"approved"`
31 | Scopes []types.Scope `json:"scopes,omitempty"`
32 | ResponseType string `json:"response_type,omitempty"`
33 | State string `json:"state,omitempty"`
34 | Nonce string `json:"nonce,omitempty"`
35 | Display string `json:"display,omitempty"`
36 | }
37 |
--------------------------------------------------------------------------------
/internal/domain/userconsent/repository.go:
--------------------------------------------------------------------------------
1 | package domain
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/vigiloauth/vigilo/v2/internal/types"
7 | )
8 |
9 | // UserConsentRepository defines the interface for storing and managing user consent data.
10 | type UserConsentRepository interface {
11 | // HasConsent checks if a user has granted consent to a client for specific scopes.
12 | //
13 | // Parameters:
14 | // - ctx Context: The context for managing timeouts and cancellations.
15 | // - userID string: The ID of the user.
16 | // - clientID string: The ID of the client application.
17 | // - requestedScope string: The requested scope(s).
18 | //
19 | // Returns:
20 | // bool: True if consent exists, false otherwise.
21 | // error: An error if the check fails, or nil if successful.
22 | HasConsent(ctx context.Context, userID string, clientID string, requestedScope types.Scope) (bool, error)
23 |
24 | // SaveConsent stores a user's consent for a client and scope.
25 | //
26 | // Parameters:
27 | // - ctx Context: The context for managing timeouts and cancellations.
28 | // - userID string: The ID of the user.
29 | // - clientID string: The ID of the client application.
30 | // - scope string: The granted scope(s).
31 | //
32 | // Returns:
33 | // - error: An error if the consent cannot be saved, or nil if successful.
34 | SaveConsent(ctx context.Context, userID string, clientID string, scope types.Scope) error
35 |
36 | // RevokeConsent removes a user's consent for a client.
37 | //
38 | // Parameters:
39 | // - ctx Context: The context for managing timeouts and cancellations.
40 | // - userID string: The ID of the user.
41 | // - clientID string: The ID of the client application.
42 | //
43 | // Returns:
44 | // - error: An error if the consent cannot be revoked, or nil if successful.
45 | RevokeConsent(ctx context.Context, userID string, clientID string) error
46 | }
47 |
--------------------------------------------------------------------------------
/internal/errors/collection.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import "fmt"
4 |
5 | type ErrorCollection struct {
6 | errors []error
7 | }
8 |
9 | // NewErrorCollection creates a new collection of errors
10 | func NewErrorCollection() *ErrorCollection {
11 | return &ErrorCollection{
12 | errors: []error{},
13 | }
14 | }
15 |
16 | // Add adds an error to the collection
17 | func (ec *ErrorCollection) Add(err error) {
18 | ec.errors = append(ec.errors, err)
19 | }
20 |
21 | // Errors returns the list of validation errors
22 | func (ec *ErrorCollection) Errors() *[]error {
23 | return &ec.errors
24 | }
25 |
26 | // HasErrors checks if there are any validation errors
27 | func (ec *ErrorCollection) HasErrors() bool {
28 | return len(ec.errors) > 0
29 | }
30 |
31 | // Error implements the error interface
32 | func (ec *ErrorCollection) Error() string {
33 | return fmt.Sprintf("%d errors", len(ec.errors))
34 | }
35 |
--------------------------------------------------------------------------------
/internal/middleware/rate_limiter.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "github.com/vigiloauth/vigilo/v2/idp/config"
8 | )
9 |
10 | // RateLimiter implements a token bucket rate limiting algorithm.
11 | // RateLimiter implements a token bucket rate limiting algorithm.
12 | type RateLimiter struct {
13 | rate float64 // Number of tokens to add per second
14 | capacity float64 // Maximum number of tokens that can be stored
15 | tokens float64 // Current number of available tokens
16 | lastUpdate time.Time // Timestamp of the last token update
17 | mu sync.Mutex // Mutex to protect concurrent access
18 |
19 | logger *config.Logger
20 | module string
21 | }
22 |
23 | // NewRateLimiter creates a new RateLimiter instance.
24 | func NewRateLimiter(rate int) *RateLimiter {
25 | return &RateLimiter{
26 | rate: float64(rate),
27 | capacity: float64(rate),
28 | tokens: float64(rate),
29 | lastUpdate: time.Now(),
30 | logger: config.GetServerConfig().Logger(),
31 | module: "Rate Limiter",
32 | }
33 | }
34 |
35 | // Allow checks if a request is allowed based on the rate limit.
36 | func (rl *RateLimiter) Allow(requestID string) bool {
37 | rl.logger.Debug(rl.module, requestID, "[Allow]: Verifying if the request exceeds the rate limit")
38 | rl.mu.Lock()
39 | defer rl.mu.Unlock()
40 |
41 | now := time.Now()
42 | elapsed := now.Sub(rl.lastUpdate).Seconds()
43 | rl.lastUpdate = now
44 |
45 | // Add tokens based on the elapsed time
46 | rl.tokens += elapsed * rl.rate
47 |
48 | // Cap tokens at the maximum capacity
49 | if rl.tokens > rl.capacity {
50 | rl.tokens = rl.capacity
51 | }
52 |
53 | if rl.tokens >= 1.0 {
54 | rl.logger.Debug(rl.module, requestID, "[Allow]: Request is valid")
55 | rl.tokens -= 1.0 // Consume a token
56 | return true
57 | }
58 |
59 | rl.logger.Warn(rl.module, requestID, "[Allow]: Request is invalid as it exceeds the rate limit")
60 | return false
61 | }
62 |
--------------------------------------------------------------------------------
/internal/middleware/rate_limiter_test.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | const testRequestID string = "requestID"
11 |
12 | func TestRateLimiter_Allow(t *testing.T) {
13 | rate := 2
14 | rl := NewRateLimiter(rate)
15 | assert.True(t, rl.Allow(testRequestID))
16 | assert.True(t, rl.Allow(testRequestID))
17 | assert.False(t, rl.Allow(testRequestID))
18 | }
19 |
20 | func TestRateLimiter_TokenRefill(t *testing.T) {
21 | rate := 1
22 | rl := NewRateLimiter(rate)
23 | assert.True(t, rl.Allow(testRequestID))
24 | assert.False(t, rl.Allow(testRequestID))
25 | time.Sleep(2 * time.Second)
26 | assert.True(t, rl.Allow(testRequestID))
27 | }
28 |
--------------------------------------------------------------------------------
/internal/mocks/audit/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/audit"
8 | )
9 |
10 | var _ domain.AuditRepository = (*MockAuditRepository)(nil)
11 |
12 | type MockAuditRepository struct {
13 | StoreAuditEventFunc func(ctx context.Context, event *domain.AuditEvent) error
14 | GetAuditEventsFunc func(ctx context.Context, filters map[string]any, from time.Time, to time.Time, limit, offset int) ([]*domain.AuditEvent, error)
15 | DeleteEventFunc func(ctx context.Context, eventID string) error
16 | }
17 |
18 | func (m *MockAuditRepository) StoreAuditEvent(ctx context.Context, event *domain.AuditEvent) error {
19 | return m.StoreAuditEventFunc(ctx, event)
20 | }
21 |
22 | func (m *MockAuditRepository) GetAuditEvents(ctx context.Context, filters map[string]any, from time.Time, to time.Time, limit, offset int) ([]*domain.AuditEvent, error) {
23 | return m.GetAuditEventsFunc(ctx, filters, from, to, limit, offset)
24 | }
25 |
26 | func (m *MockAuditRepository) DeleteEvent(ctx context.Context, eventID string) error {
27 | return m.DeleteEventFunc(ctx, eventID)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/mocks/audit/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/audit"
8 | )
9 |
10 | var _ domain.AuditLogger = (*MockAuditLogger)(nil)
11 |
12 | type MockAuditLogger struct {
13 | StoreEventFunc func(ctx context.Context, eventType domain.EventType, success bool, action domain.ActionType, method domain.MethodType, err error)
14 | DeleteOldEventsFunc func(cts context.Context, olderThan time.Time) error
15 | GetAuditEventsFunc func(ctx context.Context, filters map[string]any, from string, to string, limit, offset int) ([]*domain.AuditEvent, error)
16 | }
17 |
18 | func (m *MockAuditLogger) StoreEvent(ctx context.Context, eventType domain.EventType, success bool, action domain.ActionType, method domain.MethodType, err error) {
19 | m.StoreEventFunc(ctx, eventType, success, action, method, err)
20 | }
21 |
22 | func (m *MockAuditLogger) DeleteOldEvents(ctx context.Context, olderThan time.Time) error {
23 | return m.DeleteOldEventsFunc(ctx, olderThan)
24 | }
25 |
26 | func (m *MockAuditLogger) GetAuditEvents(ctx context.Context, filters map[string]any, from string, to string, limit, offset int) ([]*domain.AuditEvent, error) {
27 | return m.GetAuditEventsFunc(ctx, filters, from, to, limit, offset)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/mocks/authorization/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | authz "github.com/vigiloauth/vigilo/v2/internal/domain/authorization"
7 | authzCode "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
8 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
9 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
10 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
11 | )
12 |
13 | var _ authz.AuthorizationService = (*MockAuthorizationService)(nil)
14 |
15 | type MockAuthorizationService struct {
16 | AuthorizeClientFunc func(ctx context.Context, authorizationRequest *client.ClientAuthorizationRequest) (string, error)
17 | AuthorizeTokenExchangeFunc func(ctx context.Context, tokenRequest *token.TokenRequest) (*authzCode.AuthorizationCodeData, error)
18 | AuthorizeUserInfoRequestFunc func(ctx context.Context, accessTokenClaims *token.TokenClaims) (*user.User, error)
19 | UpdateAuthorizationCodeFunc func(ctx context.Context, authzCode *authzCode.AuthorizationCodeData) error
20 | }
21 |
22 | func (m *MockAuthorizationService) AuthorizeClient(ctx context.Context, authorizationRequest *client.ClientAuthorizationRequest) (string, error) {
23 | return m.AuthorizeClientFunc(ctx, authorizationRequest)
24 | }
25 |
26 | func (m *MockAuthorizationService) AuthorizeTokenExchange(ctx context.Context, tokenRequest *token.TokenRequest) (*authzCode.AuthorizationCodeData, error) {
27 | return m.AuthorizeTokenExchangeFunc(ctx, tokenRequest)
28 | }
29 |
30 | func (m *MockAuthorizationService) AuthorizeUserInfoRequest(ctx context.Context, accessTokenClaims *token.TokenClaims) (*user.User, error) {
31 | return m.AuthorizeUserInfoRequestFunc(ctx, accessTokenClaims)
32 | }
33 |
34 | func (m *MockAuthorizationService) UpdateAuthorizationCode(ctx context.Context, authData *authzCode.AuthorizationCodeData) error {
35 | return m.UpdateAuthorizationCodeFunc(ctx, authData)
36 | }
37 |
--------------------------------------------------------------------------------
/internal/mocks/authzcode/creator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
7 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
8 | )
9 |
10 | var _ domain.AuthorizationCodeCreator = (*MockAuthorizationCodeCreator)(nil)
11 |
12 | type MockAuthorizationCodeCreator struct {
13 | GenerateAuthorizationCodeFunc func(ctx context.Context, request *client.ClientAuthorizationRequest) (string, error)
14 | }
15 |
16 | func (m *MockAuthorizationCodeCreator) GenerateAuthorizationCode(ctx context.Context, request *client.ClientAuthorizationRequest) (string, error) {
17 | return m.GenerateAuthorizationCodeFunc(ctx, request)
18 | }
19 |
--------------------------------------------------------------------------------
/internal/mocks/authzcode/issuer.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
7 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
8 | )
9 |
10 | var _ domain.AuthorizationCodeIssuer = (*MockAuthorizationCodeIssuer)(nil)
11 |
12 | type MockAuthorizationCodeIssuer struct {
13 | IssueAuthorizationCodeFunc func(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error)
14 | }
15 |
16 | func (m *MockAuthorizationCodeIssuer) IssueAuthorizationCode(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error) {
17 | return m.IssueAuthorizationCodeFunc(ctx, req)
18 | }
19 |
--------------------------------------------------------------------------------
/internal/mocks/authzcode/manager.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | authzCode "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
7 | )
8 |
9 | var _ authzCode.AuthorizationCodeManager = (*MockAuthorizationCodeManager)(nil)
10 |
11 | type MockAuthorizationCodeManager struct {
12 | RevokeAuthorizationCodeFunc func(ctx context.Context, code string) error
13 | GetAuthorizationCodeFunc func(ctx context.Context, code string) (*authzCode.AuthorizationCodeData, error)
14 | UpdateAuthorizationCodeFunc func(ctx context.Context, authData *authzCode.AuthorizationCodeData) error
15 | }
16 |
17 | func (m *MockAuthorizationCodeManager) RevokeAuthorizationCode(ctx context.Context, code string) error {
18 | return m.RevokeAuthorizationCodeFunc(ctx, code)
19 | }
20 |
21 | func (m *MockAuthorizationCodeManager) GetAuthorizationCode(ctx context.Context, code string) (*authzCode.AuthorizationCodeData, error) {
22 | return m.GetAuthorizationCodeFunc(ctx, code)
23 | }
24 |
25 | func (m *MockAuthorizationCodeManager) UpdateAuthorizationCode(ctx context.Context, authData *authzCode.AuthorizationCodeData) error {
26 | return m.UpdateAuthorizationCodeFunc(ctx, authData)
27 | }
28 |
--------------------------------------------------------------------------------
/internal/mocks/authzcode/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | authz "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
8 | )
9 |
10 | var _ authz.AuthorizationCodeRepository = (*MockAuthorizationCodeRepository)(nil)
11 |
12 | type MockAuthorizationCodeRepository struct {
13 | StoreAuthorizationCodeFunc func(ctx context.Context, code string, data *authz.AuthorizationCodeData, expiresAt time.Time) error
14 | GetAuthorizationCodeFunc func(ctx context.Context, code string) (*authz.AuthorizationCodeData, error)
15 | DeleteAuthorizationCodeFunc func(ctx context.Context, code string) error
16 | UpdateAuthorizationCodeFunc func(ctx context.Context, code string, authData *authz.AuthorizationCodeData) error
17 | }
18 |
19 | func (m *MockAuthorizationCodeRepository) StoreAuthorizationCode(ctx context.Context, code string, data *authz.AuthorizationCodeData, expiresAt time.Time) error {
20 | return m.StoreAuthorizationCodeFunc(ctx, code, data, expiresAt)
21 | }
22 |
23 | func (m *MockAuthorizationCodeRepository) GetAuthorizationCode(ctx context.Context, code string) (*authz.AuthorizationCodeData, error) {
24 | return m.GetAuthorizationCodeFunc(ctx, code)
25 | }
26 |
27 | func (m *MockAuthorizationCodeRepository) DeleteAuthorizationCode(ctx context.Context, code string) error {
28 | return m.DeleteAuthorizationCodeFunc(ctx, code)
29 | }
30 |
31 | func (m *MockAuthorizationCodeRepository) UpdateAuthorizationCode(ctx context.Context, code string, authData *authz.AuthorizationCodeData) error {
32 | return m.UpdateAuthorizationCodeFunc(ctx, code, authData)
33 | }
34 |
--------------------------------------------------------------------------------
/internal/mocks/authzcode/validator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
7 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
8 | )
9 |
10 | var _ domain.AuthorizationCodeValidator = (*MockAuthorizationCodeRequestValidator)(nil)
11 |
12 | type MockAuthorizationCodeRequestValidator struct {
13 | ValidateRequestFunc func(ctx context.Context, req *client.ClientAuthorizationRequest) error
14 | ValidateAuthorizationCodeFunc func(ctx context.Context, code, clientID, redirectURI string) error
15 | ValidatePKCEFunc func(ctx context.Context, authzCodeData *domain.AuthorizationCodeData, codeVerifier string) error
16 | }
17 |
18 | func (m *MockAuthorizationCodeRequestValidator) ValidateRequest(ctx context.Context, req *client.ClientAuthorizationRequest) error {
19 | return m.ValidateRequestFunc(ctx, req)
20 | }
21 |
22 | func (m *MockAuthorizationCodeRequestValidator) ValidateAuthorizationCode(ctx context.Context, code, clientID, redirectURI string) error {
23 | return m.ValidateAuthorizationCodeFunc(ctx, code, clientID, redirectURI)
24 | }
25 |
26 | func (m *MockAuthorizationCodeRequestValidator) ValidatePKCE(ctx context.Context, authzCodeData *domain.AuthorizationCodeData, codeVerifier string) error {
27 | return m.ValidatePKCEFunc(ctx, authzCodeData, codeVerifier)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/mocks/client/authenticator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/client"
8 | "github.com/vigiloauth/vigilo/v2/internal/types"
9 | )
10 |
11 | var _ domain.ClientAuthenticator = (*MockClientAuthenticator)(nil)
12 |
13 | type MockClientAuthenticator struct {
14 | AuthenticateRequestFunc func(ctx context.Context, r *http.Request, requiredScope types.Scope) error
15 | AuthenticateClientFunc func(ctx context.Context, req *domain.ClientAuthenticationRequest) error
16 | }
17 |
18 | func (m *MockClientAuthenticator) AuthenticateRequest(ctx context.Context, r *http.Request, requiredScope types.Scope) error {
19 | return m.AuthenticateRequestFunc(ctx, r, requiredScope)
20 | }
21 |
22 | func (m *MockClientAuthenticator) AuthenticateClient(ctx context.Context, req *domain.ClientAuthenticationRequest) error {
23 | return m.AuthenticateClientFunc(ctx, req)
24 | }
25 |
--------------------------------------------------------------------------------
/internal/mocks/client/authorization.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | var _ domain.ClientAuthorization = (*MockClientAuthorization)(nil)
10 |
11 | type MockClientAuthorization struct {
12 | AuthorizeFunc func(ctx context.Context, request *domain.ClientAuthorizationRequest) (string, error)
13 | }
14 |
15 | func (m *MockClientAuthorization) Authorize(ctx context.Context, request *domain.ClientAuthorizationRequest) (string, error) {
16 | return m.AuthorizeFunc(ctx, request)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/client/creator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | var _ domain.ClientCreator = (*MockClientCreator)(nil)
10 |
11 | type MockClientCreator struct {
12 | RegisterFunc func(ctx context.Context, client *domain.ClientRegistrationRequest) (*domain.ClientRegistrationResponse, error)
13 | }
14 |
15 | func (m *MockClientCreator) Register(ctx context.Context, client *domain.ClientRegistrationRequest) (*domain.ClientRegistrationResponse, error) {
16 | return m.RegisterFunc(ctx, client)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/client/manager.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | var _ domain.ClientManager = (*MockClientManager)(nil)
10 |
11 | type MockClientManager struct {
12 | RegenerateClientSecretFunc func(ctx context.Context, clientID string) (*domain.ClientSecretRegenerationResponse, error)
13 | GetClientByIDFunc func(ctx context.Context, clientID string) (*domain.Client, error)
14 | GetClientInformationFunc func(ctx context.Context, clientID string, registrationAccessToken string) (*domain.ClientInformationResponse, error)
15 | UpdateClientInformationFunc func(ctx context.Context, clientID string, registrationAccessToken string, req *domain.ClientUpdateRequest) (*domain.ClientInformationResponse, error)
16 | DeleteClientInformationFunc func(ctx context.Context, clientID string, registrationAccessToken string) error
17 | }
18 |
19 | func (m *MockClientManager) RegenerateClientSecret(ctx context.Context, clientID string) (*domain.ClientSecretRegenerationResponse, error) {
20 | return m.RegenerateClientSecretFunc(ctx, clientID)
21 | }
22 |
23 | func (m *MockClientManager) GetClientByID(ctx context.Context, clientID string) (*domain.Client, error) {
24 | return m.GetClientByIDFunc(ctx, clientID)
25 | }
26 |
27 | func (m *MockClientManager) GetClientInformation(ctx context.Context, clientID string, registrationAccessToken string) (*domain.ClientInformationResponse, error) {
28 | return m.GetClientInformationFunc(ctx, clientID, registrationAccessToken)
29 | }
30 |
31 | func (m *MockClientManager) UpdateClientInformation(ctx context.Context, clientID string, registrationAccessToken string, req *domain.ClientUpdateRequest) (*domain.ClientInformationResponse, error) {
32 | return m.UpdateClientInformationFunc(ctx, clientID, registrationAccessToken, req)
33 | }
34 |
35 | func (m *MockClientManager) DeleteClientInformation(ctx context.Context, clientID string, registrationAccessToken string) error {
36 | return m.DeleteClientInformationFunc(ctx, clientID, registrationAccessToken)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/mocks/client/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | var _ client.ClientRepository = (*MockClientRepository)(nil)
10 |
11 | type MockClientRepository struct {
12 | SaveClientFunc func(ctx context.Context, client *client.Client) error
13 | GetClientByIDFunc func(ctx context.Context, clientID string) (*client.Client, error)
14 | DeleteClientByIDFunc func(ctx context.Context, clientID string) error
15 | UpdateClientFunc func(ctx context.Context, client *client.Client) error
16 | IsExistingIDFunc func(ctx context.Context, clientID string) bool
17 | }
18 |
19 | func (m *MockClientRepository) SaveClient(ctx context.Context, client *client.Client) error {
20 | return m.SaveClientFunc(ctx, client)
21 | }
22 |
23 | func (m *MockClientRepository) GetClientByID(ctx context.Context, clientID string) (*client.Client, error) {
24 | return m.GetClientByIDFunc(ctx, clientID)
25 | }
26 |
27 | func (m *MockClientRepository) DeleteClientByID(ctx context.Context, clientID string) error {
28 | return m.DeleteClientByIDFunc(ctx, clientID)
29 | }
30 |
31 | func (m *MockClientRepository) UpdateClient(ctx context.Context, client *client.Client) error {
32 | return m.UpdateClientFunc(ctx, client)
33 | }
34 |
35 | func (m *MockClientRepository) IsExistingID(ctx context.Context, clientID string) bool {
36 | return m.IsExistingIDFunc(ctx, clientID)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/mocks/client/validator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/client"
7 | )
8 |
9 | var _ domain.ClientValidator = (*MockClientValidator)(nil)
10 |
11 | type MockClientValidator struct {
12 | ValidateRegistrationRequestFunc func(ctx context.Context, req *domain.ClientRegistrationRequest) error
13 | ValidateUpdateRequestFunc func(ctx context.Context, req *domain.ClientUpdateRequest) error
14 | ValidateAuthorizationRequestFunc func(ctx context.Context, req *domain.ClientAuthorizationRequest) error
15 | ValidateRedirectURIFunc func(ctx context.Context, redirectURI string, existingClient *domain.Client) error
16 | ValidateClientAndRegistrationAccessTokenFunc func(ctx context.Context, clientID string, registrationAccessToken string) error
17 | }
18 |
19 | func (m *MockClientValidator) ValidateRegistrationRequest(ctx context.Context, req *domain.ClientRegistrationRequest) error {
20 | return m.ValidateRegistrationRequestFunc(ctx, req)
21 | }
22 |
23 | func (m *MockClientValidator) ValidateUpdateRequest(ctx context.Context, req *domain.ClientUpdateRequest) error {
24 | return m.ValidateUpdateRequestFunc(ctx, req)
25 | }
26 |
27 | func (m *MockClientValidator) ValidateAuthorizationRequest(ctx context.Context, req *domain.ClientAuthorizationRequest) error {
28 | return m.ValidateAuthorizationRequestFunc(ctx, req)
29 | }
30 |
31 | func (m *MockClientValidator) ValidateRedirectURI(ctx context.Context, redirectURI string, existingClient *domain.Client) error {
32 | return m.ValidateRedirectURIFunc(ctx, redirectURI, existingClient)
33 | }
34 |
35 | func (m *MockClientValidator) ValidateClientAndRegistrationAccessToken(ctx context.Context, clientID string, registrationAccessToken string) error {
36 | return m.ValidateClientAndRegistrationAccessTokenFunc(ctx, clientID, registrationAccessToken)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/mocks/cookies/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "time"
7 |
8 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/cookies"
9 | )
10 |
11 | var _ domain.HTTPCookieService = (*MockHTTPCookieService)(nil)
12 |
13 | type MockHTTPCookieService struct {
14 | SetSessionCookieFunc func(ctx context.Context, w http.ResponseWriter, token string, expirationTime time.Duration)
15 | ClearSessionCookieFunc func(ctx context.Context, w http.ResponseWriter)
16 | GetSessionCookieFunc func(r *http.Request) (*http.Cookie, error)
17 | }
18 |
19 | func (m *MockHTTPCookieService) SetSessionCookie(ctx context.Context, w http.ResponseWriter, token string, expirationTime time.Duration) {
20 | m.SetSessionCookieFunc(ctx, w, token, expirationTime)
21 | }
22 |
23 | func (m *MockHTTPCookieService) ClearSessionCookie(ctx context.Context, w http.ResponseWriter) {
24 | m.ClearSessionCookieFunc(ctx, w)
25 | }
26 |
27 | func (m *MockHTTPCookieService) GetSessionCookie(r *http.Request) (*http.Cookie, error) {
28 | return m.GetSessionCookieFunc(r)
29 | }
30 |
--------------------------------------------------------------------------------
/internal/mocks/crypto/cryptographer.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import domain "github.com/vigiloauth/vigilo/v2/internal/domain/crypto"
4 |
5 | var _ domain.Cryptographer = (*MockCryptographer)(nil)
6 |
7 | type MockCryptographer struct {
8 | EncryptStringFunc func(plainStr, secretKey string) (string, error)
9 | DecryptStringFunc func(encryptedStr, secretKey string) (string, error)
10 | EncryptBytesFunc func(plainBytes []byte, secretKey string) (string, error)
11 | DecryptBytesFunc func(encryptedBytes, secretKey string) ([]byte, error)
12 | HashStringFunc func(plainStr string) (string, error)
13 | GenerateRandomStringFunc func(length int) (string, error)
14 | }
15 |
16 | func (m *MockCryptographer) EncryptString(plainStr string, secretKey string) (string, error) {
17 | return m.EncryptStringFunc(plainStr, secretKey)
18 | }
19 | func (m *MockCryptographer) DecryptString(encryptedStr string, secretKey string) (string, error) {
20 | return m.DecryptStringFunc(encryptedStr, secretKey)
21 | }
22 | func (m *MockCryptographer) EncryptBytes(plainBytes []byte, secretKey string) (string, error) {
23 | return m.EncryptBytesFunc(plainBytes, secretKey)
24 | }
25 |
26 | func (m *MockCryptographer) DecryptBytes(encryptedBytes, secretKey string) ([]byte, error) {
27 | return m.DecryptBytesFunc(encryptedBytes, secretKey)
28 | }
29 |
30 | func (m *MockCryptographer) HashString(plainStr string) (string, error) {
31 | return m.HashStringFunc(plainStr)
32 | }
33 | func (m *MockCryptographer) GenerateRandomString(length int) (string, error) {
34 | return m.GenerateRandomStringFunc(length)
35 | }
36 |
--------------------------------------------------------------------------------
/internal/mocks/email/mailer.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/email"
5 | "gopkg.in/gomail.v2"
6 | )
7 |
8 | var _ domain.Mailer = (*MockGoMailer)(nil)
9 |
10 | type MockGoMailer struct {
11 | DialFunc func(host string, port int, username string, password string) (gomail.SendCloser, error)
12 | DialAndSendFunc func(host string, port int, username string, password string, message ...*gomail.Message) error
13 | NewMessageFunc func(request *domain.EmailRequest, body string, subject string, fromAddress string) *gomail.Message
14 | }
15 |
16 | func (m *MockGoMailer) Dial(host string, port int, username string, password string) (gomail.SendCloser, error) {
17 | return m.DialFunc(host, port, username, password)
18 | }
19 |
20 | func (m *MockGoMailer) DialAndSend(host string, port int, username string, password string, message ...*gomail.Message) error {
21 | return m.DialAndSendFunc(host, port, username, password, message...)
22 | }
23 |
24 | func (m *MockGoMailer) NewMessage(request *domain.EmailRequest, body string, subject string, fromAddress string) *gomail.Message {
25 | return m.NewMessageFunc(request, body, subject, fromAddress)
26 | }
27 |
--------------------------------------------------------------------------------
/internal/mocks/email/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/email"
7 | )
8 |
9 | var _ domain.EmailService = (*MockEmailService)(nil)
10 |
11 | type MockEmailService struct {
12 | SendEmailFunc func(ctx context.Context, request *domain.EmailRequest) error
13 | TestConnectionFunc func() error
14 | GetEmailRetryQueueFunc func() *domain.EmailRetryQueue
15 | }
16 |
17 | func (m *MockEmailService) SendEmail(ctx context.Context, request *domain.EmailRequest) error {
18 | return m.SendEmailFunc(ctx, request)
19 | }
20 |
21 | func (m *MockEmailService) TestConnection() error {
22 | return m.TestConnectionFunc()
23 | }
24 |
25 | func (m *MockEmailService) GetEmailRetryQueue() *domain.EmailRetryQueue {
26 | return m.GetEmailRetryQueueFunc()
27 | }
28 |
--------------------------------------------------------------------------------
/internal/mocks/jwt/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | jwt "github.com/vigiloauth/vigilo/v2/internal/domain/jwt"
7 | tokens "github.com/vigiloauth/vigilo/v2/internal/domain/token"
8 | )
9 |
10 | var _ jwt.JWTService = (*MockJWTService)(nil)
11 |
12 | type MockJWTService struct {
13 | ParseWithClaimsFunc func(ctx context.Context, tokenString string) (*tokens.TokenClaims, error)
14 | SignTokenFunc func(ctx context.Context, claims *tokens.TokenClaims) (string, error)
15 | }
16 |
17 | func (m *MockJWTService) ParseWithClaims(ctx context.Context, tokenString string) (*tokens.TokenClaims, error) {
18 | return m.ParseWithClaimsFunc(ctx, tokenString)
19 | }
20 |
21 | func (m *MockJWTService) SignToken(ctx context.Context, claims *tokens.TokenClaims) (string, error) {
22 | return m.SignTokenFunc(ctx, claims)
23 | }
24 |
--------------------------------------------------------------------------------
/internal/mocks/login/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/login"
7 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
8 | )
9 |
10 | var _ domain.LoginAttemptRepository = (*MockLoginAttemptRepository)(nil)
11 |
12 | type MockLoginAttemptRepository struct {
13 | SaveLoginAttemptFunc func(ctx context.Context, attempt *user.UserLoginAttempt) error
14 | GetLoginAttemptsByUserIDFunc func(ctx context.Context, userID string) ([]*user.UserLoginAttempt, error)
15 | }
16 |
17 | func (m *MockLoginAttemptRepository) SaveLoginAttempt(ctx context.Context, attempt *user.UserLoginAttempt) error {
18 | return m.SaveLoginAttemptFunc(ctx, attempt)
19 | }
20 |
21 | func (m *MockLoginAttemptRepository) GetLoginAttemptsByUserID(ctx context.Context, userID string) ([]*user.UserLoginAttempt, error) {
22 | return m.GetLoginAttemptsByUserIDFunc(ctx, userID)
23 | }
24 |
--------------------------------------------------------------------------------
/internal/mocks/login/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/login"
7 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
8 | )
9 |
10 | var _ domain.LoginAttemptService = (*MockLoginAttemptService)(nil)
11 |
12 | type MockLoginAttemptService struct {
13 | SaveLoginAttemptFunc func(ctx context.Context, attempt *user.UserLoginAttempt) error
14 | GetLoginAttemptsByUserIDFunc func(ctx context.Context, userID string) ([]*user.UserLoginAttempt, error)
15 | HandleFailedLoginAttemptFunc func(ctx context.Context, user *user.User, attempt *user.UserLoginAttempt) error
16 | }
17 |
18 | func (m *MockLoginAttemptService) SaveLoginAttempt(ctx context.Context, attempt *user.UserLoginAttempt) error {
19 | return m.SaveLoginAttemptFunc(ctx, attempt)
20 | }
21 |
22 | func (m *MockLoginAttemptService) GetLoginAttemptsByUserID(ctx context.Context, userID string) ([]*user.UserLoginAttempt, error) {
23 | return m.GetLoginAttemptsByUserIDFunc(ctx, userID)
24 | }
25 |
26 | func (m *MockLoginAttemptService) HandleFailedLoginAttempt(ctx context.Context, user *user.User, attempt *user.UserLoginAttempt) error {
27 | return m.HandleFailedLoginAttemptFunc(ctx, user, attempt)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/mocks/session/manager.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/session"
8 | )
9 |
10 | var _ domain.SessionManager = (*MockSessionManager)(nil)
11 |
12 | type MockSessionManager struct {
13 | GetUserIDFromSessionFunc func(ctx context.Context, r *http.Request) (string, error)
14 | GetUserAuthenticationTimeFunc func(ctx context.Context, r *http.Request) (int64, error)
15 | }
16 |
17 | func (m *MockSessionManager) GetUserIDFromSession(ctx context.Context, r *http.Request) (string, error) {
18 | return m.GetUserIDFromSessionFunc(ctx, r)
19 | }
20 |
21 | func (m *MockSessionManager) GetUserAuthenticationTime(ctx context.Context, r *http.Request) (int64, error) {
22 | return m.GetUserAuthenticationTimeFunc(ctx, r)
23 | }
24 |
--------------------------------------------------------------------------------
/internal/mocks/session/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | session "github.com/vigiloauth/vigilo/v2/internal/domain/session"
7 | )
8 |
9 | var _ session.SessionRepository = (*MockSessionRepository)(nil)
10 |
11 | type MockSessionRepository struct {
12 | SaveSessionFunc func(ctx context.Context, sessionData *session.SessionData) error
13 | GetSessionByIDFunc func(ctx context.Context, sessionID string) (*session.SessionData, error)
14 | UpdateSessionByIDFunc func(ctx context.Context, sessionID string, sessionData *session.SessionData) error
15 | DeleteSessionByIDFunc func(ctx context.Context, sessionID string) error
16 | }
17 |
18 | func (m *MockSessionRepository) SaveSession(ctx context.Context, sessionData *session.SessionData) error {
19 | return m.SaveSessionFunc(ctx, sessionData)
20 | }
21 |
22 | func (m *MockSessionRepository) GetSessionByID(ctx context.Context, sessionID string) (*session.SessionData, error) {
23 | return m.GetSessionByIDFunc(ctx, sessionID)
24 | }
25 |
26 | func (m *MockSessionRepository) UpdateSessionByID(ctx context.Context, sessionID string, sessionData *session.SessionData) error {
27 | return m.UpdateSessionByIDFunc(ctx, sessionID, sessionData)
28 | }
29 |
30 | func (m *MockSessionRepository) DeleteSessionByID(ctx context.Context, sessionID string) error {
31 | return m.DeleteSessionByIDFunc(ctx, sessionID)
32 | }
33 |
--------------------------------------------------------------------------------
/internal/mocks/session/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "net/http"
5 |
6 | session "github.com/vigiloauth/vigilo/v2/internal/domain/session"
7 | )
8 |
9 | var _ session.SessionService = (*MockSessionService)(nil)
10 |
11 | type MockSessionService struct {
12 | CreateSessionFunc func(w http.ResponseWriter, r *http.Request, sessionData *session.SessionData) error
13 | InvalidateSessionFunc func(w http.ResponseWriter, r *http.Request) error
14 | GetUserIDFromSessionFunc func(r *http.Request) (string, error)
15 | UpdateSessionFunc func(r *http.Request, sessionData *session.SessionData) error
16 | GetSessionDataFunc func(r *http.Request) (*session.SessionData, error)
17 | }
18 |
19 | func (m *MockSessionService) CreateSession(w http.ResponseWriter, r *http.Request, sessionData *session.SessionData) error {
20 | return m.CreateSessionFunc(w, r, sessionData)
21 | }
22 |
23 | func (m *MockSessionService) InvalidateSession(w http.ResponseWriter, r *http.Request) error {
24 | return m.InvalidateSessionFunc(w, r)
25 | }
26 |
27 | func (m *MockSessionService) GetUserIDFromSession(r *http.Request) (string, error) {
28 | return m.GetUserIDFromSessionFunc(r)
29 | }
30 |
31 | func (m *MockSessionService) UpdateSession(r *http.Request, sessionData *session.SessionData) error {
32 | return m.UpdateSessionFunc(r, sessionData)
33 | }
34 |
35 | func (m *MockSessionService) GetSessionData(r *http.Request) (*session.SessionData, error) {
36 | return m.GetSessionDataFunc(r)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/mocks/token/creator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | claims "github.com/vigiloauth/vigilo/v2/internal/domain/claims"
8 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
9 | "github.com/vigiloauth/vigilo/v2/internal/types"
10 | )
11 |
12 | var _ token.TokenCreator = (*MockTokenCreator)(nil)
13 |
14 | type MockTokenCreator struct {
15 | CreateAccessTokenFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error)
16 | CreateRefreshTokenFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error)
17 | CreateAccessTokenWithClaimsFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string, claims *claims.ClaimsRequest) (string, error)
18 | CreateIDTokenFunc func(ctx context.Context, userID string, clientID string, scopes types.Scope, nonce string, acrValues string, authTime time.Time) (string, error)
19 | }
20 |
21 | func (m *MockTokenCreator) CreateAccessToken(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error) {
22 | return m.CreateAccessTokenFunc(ctx, subject, audience, scopes, roles, nonce)
23 | }
24 |
25 | func (m *MockTokenCreator) CreateRefreshToken(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error) {
26 | return m.CreateRefreshTokenFunc(ctx, subject, audience, scopes, roles, nonce)
27 | }
28 |
29 | func (m *MockTokenCreator) CreateAccessTokenWithClaims(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string, claims *claims.ClaimsRequest) (string, error) {
30 | return m.CreateAccessTokenWithClaimsFunc(ctx, subject, audience, scopes, roles, nonce, claims)
31 | }
32 |
33 | func (m *MockTokenCreator) CreateIDToken(ctx context.Context, userID string, clientID string, scopes types.Scope, nonce string, acrValues string, authTime time.Time) (string, error) {
34 | return m.CreateIDTokenFunc(ctx, userID, clientID, scopes, nonce, acrValues, authTime)
35 | }
36 |
--------------------------------------------------------------------------------
/internal/mocks/token/issuer.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | claims "github.com/vigiloauth/vigilo/v2/internal/domain/claims"
8 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
9 | "github.com/vigiloauth/vigilo/v2/internal/types"
10 | )
11 |
12 | var _ token.TokenIssuer = (*MockTokenIssuer)(nil)
13 |
14 | type MockTokenIssuer struct {
15 | IssueTokenPairFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string, claims *claims.ClaimsRequest) (string, string, error)
16 | IssueIDTokenFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, nonce string, acrValues string, authTime time.Time) (string, error)
17 | IssueAccessTokenFunc func(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error)
18 | }
19 |
20 | func (m *MockTokenIssuer) IssueTokenPair(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string, claims *claims.ClaimsRequest) (string, string, error) {
21 | return m.IssueTokenPairFunc(ctx, subject, audience, scopes, roles, nonce, claims)
22 | }
23 |
24 | func (m *MockTokenIssuer) IssueIDToken(ctx context.Context, subject string, audience string, scopes types.Scope, nonce string, acrValues string, authTime time.Time) (string, error) {
25 | return m.IssueIDTokenFunc(ctx, subject, audience, scopes, nonce, acrValues, authTime)
26 | }
27 |
28 | func (m *MockTokenIssuer) IssueAccessToken(ctx context.Context, subject string, audience string, scopes types.Scope, roles string, nonce string) (string, error) {
29 | return m.IssueAccessTokenFunc(ctx, subject, audience, scopes, roles, nonce)
30 | }
31 |
--------------------------------------------------------------------------------
/internal/mocks/token/manager.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | tokens "github.com/vigiloauth/vigilo/v2/internal/domain/token"
7 | )
8 |
9 | var _ tokens.TokenManager = (*MockTokenManager)(nil)
10 |
11 | type MockTokenManager struct {
12 | IntrospectFunc func(ctx context.Context, tokenStr string) *tokens.TokenIntrospectionResponse
13 | RevokeFunc func(ctx context.Context, tokenStr string) error
14 | GetTokenDataFunc func(ctx context.Context, tokenStr string) (*tokens.TokenData, error)
15 | DeleteTokenFunc func(ctx context.Context, token string) error
16 | BlacklistTokenFunc func(ctx context.Context, token string) error
17 | DeleteExpiredTokensFunc func(ctx context.Context) error
18 | }
19 |
20 | func (m *MockTokenManager) Introspect(ctx context.Context, tokenStr string) *tokens.TokenIntrospectionResponse {
21 | return m.IntrospectFunc(ctx, tokenStr)
22 | }
23 |
24 | func (m *MockTokenManager) Revoke(ctx context.Context, tokenStr string) error {
25 | return m.RevokeFunc(ctx, tokenStr)
26 | }
27 |
28 | func (m *MockTokenManager) GetTokenData(ctx context.Context, tokenStr string) (*tokens.TokenData, error) {
29 | return m.GetTokenDataFunc(ctx, tokenStr)
30 | }
31 |
32 | func (m *MockTokenManager) DeleteToken(ctx context.Context, token string) error {
33 | return m.DeleteTokenFunc(ctx, token)
34 | }
35 |
36 | func (m *MockTokenManager) BlacklistToken(ctx context.Context, token string) error {
37 | return m.BlacklistTokenFunc(ctx, token)
38 | }
39 |
40 | func (m *MockTokenManager) DeleteExpiredTokens(ctx context.Context) error {
41 | return m.DeleteExpiredTokensFunc(ctx)
42 | }
43 |
--------------------------------------------------------------------------------
/internal/mocks/token/parser.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
7 | )
8 |
9 | var _ token.TokenParser = (*MockTokenParser)(nil)
10 |
11 | type MockTokenParser struct {
12 | ParseTokenFunc func(ctx context.Context, tokenString string) (*token.TokenClaims, error)
13 | }
14 |
15 | func (m *MockTokenParser) ParseToken(ctx context.Context, tokenStr string) (*token.TokenClaims, error) {
16 | return m.ParseTokenFunc(ctx, tokenStr)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/token/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
8 | )
9 |
10 | var _ token.TokenRepository = (*MockTokenRepository)(nil)
11 |
12 | type MockTokenRepository struct {
13 | SaveTokenFunc func(ctx context.Context, token string, id string, tokenData *token.TokenData, expiration time.Time) error
14 | IsTokenBlacklistedFunc func(ctx context.Context, token string) (bool, error)
15 | GetTokenFunc func(ctx context.Context, token string) (*token.TokenData, error)
16 | DeleteTokenFunc func(ctx context.Context, token string) error
17 | BlacklistTokenFunc func(ctx context.Context, token string) error
18 | ExistsByTokenIDFunc func(ctx context.Context, tokenID string) (bool, error)
19 | GetExpiredTokensFunc func(ctx context.Context) ([]*token.TokenData, error)
20 | }
21 |
22 | func (m *MockTokenRepository) SaveToken(ctx context.Context, token string, id string, tokenData *token.TokenData, expiration time.Time) error {
23 | return m.SaveTokenFunc(ctx, token, id, tokenData, expiration)
24 | }
25 |
26 | func (m *MockTokenRepository) IsTokenBlacklisted(ctx context.Context, token string) (bool, error) {
27 | return m.IsTokenBlacklistedFunc(ctx, token)
28 | }
29 |
30 | func (m *MockTokenRepository) GetToken(ctx context.Context, token string) (*token.TokenData, error) {
31 | return m.GetTokenFunc(ctx, token)
32 | }
33 |
34 | func (m *MockTokenRepository) DeleteToken(ctx context.Context, token string) error {
35 | return m.DeleteTokenFunc(ctx, token)
36 | }
37 |
38 | func (m *MockTokenRepository) BlacklistToken(ctx context.Context, token string) error {
39 | return m.BlacklistTokenFunc(ctx, token)
40 | }
41 |
42 | func (m *MockTokenRepository) ExistsByTokenID(ctx context.Context, tokenID string) (bool, error) {
43 | return m.ExistsByTokenIDFunc(ctx, tokenID)
44 | }
45 |
46 | func (m *MockTokenRepository) GetExpiredTokens(ctx context.Context) ([]*token.TokenData, error) {
47 | return m.GetExpiredTokensFunc(ctx)
48 | }
49 |
--------------------------------------------------------------------------------
/internal/mocks/token/validator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
7 | )
8 |
9 | var _ token.TokenValidator = (*MockTokenValidator)(nil)
10 |
11 | type MockTokenValidator struct {
12 | ValidateTokenFunc func(ctx context.Context, tokenStr string) error
13 | }
14 |
15 | func (m *MockTokenValidator) ValidateToken(ctx context.Context, tokenStr string) error {
16 | return m.ValidateTokenFunc(ctx, tokenStr)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/user/authenticator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | users "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | var _ users.UserAuthenticator = (*MockUserAuthenticator)(nil)
10 |
11 | type MockUserAuthenticator struct {
12 | AuthenticateUserFunc func(ctx context.Context, request *users.UserLoginRequest) (*users.UserLoginResponse, error)
13 | }
14 |
15 | func (m *MockUserAuthenticator) AuthenticateUser(ctx context.Context, request *users.UserLoginRequest) (*users.UserLoginResponse, error) {
16 | return m.AuthenticateUserFunc(ctx, request)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/user/creator.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | users "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | var _ users.UserCreator = (*MockUserCreator)(nil)
10 |
11 | type MockUserCreator struct {
12 | CreateUserFunc func(ctx context.Context, user *users.User) (*users.UserRegistrationResponse, error)
13 | }
14 |
15 | func (m *MockUserCreator) CreateUser(ctx context.Context, user *users.User) (*users.UserRegistrationResponse, error) {
16 | return m.CreateUserFunc(ctx, user)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/user/manager.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | users "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | var _ users.UserManager = (*MockUserManager)(nil)
10 |
11 | type MockUserManager struct {
12 | GetUserByUsernameFunc func(ctx context.Context, username string) (*users.User, error)
13 | GetUserByIDFunc func(ctx context.Context, userID string) (*users.User, error)
14 | DeleteUnverifiedUsersFunc func(ctx context.Context) error
15 | ResetPasswordFunc func(ctx context.Context, userEmail, newPassword, resetToken string) (*users.UserPasswordResetResponse, error)
16 | }
17 |
18 | func (m *MockUserManager) GetUserByUsername(ctx context.Context, username string) (*users.User, error) {
19 | return m.GetUserByUsernameFunc(ctx, username)
20 | }
21 |
22 | func (m *MockUserManager) GetUserByID(ctx context.Context, userID string) (*users.User, error) {
23 | return m.GetUserByIDFunc(ctx, userID)
24 | }
25 |
26 | func (m *MockUserManager) DeleteUnverifiedUsers(ctx context.Context) error {
27 | return m.DeleteUnverifiedUsersFunc(ctx)
28 | }
29 |
30 | func (m *MockUserManager) ResetPassword(
31 | ctx context.Context,
32 | userEmail string,
33 | newPassword string,
34 | resetToken string,
35 | ) (*users.UserPasswordResetResponse, error) {
36 | return m.ResetPasswordFunc(ctx, userEmail, newPassword, resetToken)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/mocks/user/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | user "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | var _ user.UserRepository = (*MockUserRepository)(nil)
10 |
11 | type MockUserRepository struct {
12 | AddUserFunc func(ctx context.Context, user *user.User) error
13 | GetUserByIDFunc func(ctx context.Context, userID string) (*user.User, error)
14 | DeleteUserByIDFunc func(ctx context.Context, userID string) error
15 | UpdateUserFunc func(ctx context.Context, user *user.User) error
16 | GetUserByEmailFunc func(ctx context.Context, email string) (*user.User, error)
17 | GetUserByUsernameFunc func(ctx context.Context, username string) (*user.User, error)
18 | FindUnverifiedUsersOlderThanWeekFunc func(ctx context.Context) ([]*user.User, error)
19 | }
20 |
21 | func (m *MockUserRepository) AddUser(ctx context.Context, user *user.User) error {
22 | return m.AddUserFunc(ctx, user)
23 | }
24 |
25 | func (m *MockUserRepository) GetUserByID(ctx context.Context, userID string) (*user.User, error) {
26 | return m.GetUserByIDFunc(ctx, userID)
27 | }
28 |
29 | func (m *MockUserRepository) DeleteUserByID(ctx context.Context, userID string) error {
30 | return m.DeleteUserByIDFunc(ctx, userID)
31 | }
32 |
33 | func (m *MockUserRepository) UpdateUser(ctx context.Context, user *user.User) error {
34 | return m.UpdateUserFunc(ctx, user)
35 | }
36 |
37 | func (m *MockUserRepository) GetUserByEmail(ctx context.Context, email string) (*user.User, error) {
38 | return m.GetUserByEmailFunc(ctx, email)
39 | }
40 |
41 | func (m *MockUserRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) {
42 | return m.GetUserByUsernameFunc(ctx, username)
43 | }
44 |
45 | func (m *MockUserRepository) FindUnverifiedUsersOlderThanWeek(ctx context.Context) ([]*user.User, error) {
46 | return m.FindUnverifiedUsersOlderThanWeekFunc(ctx)
47 | }
48 |
--------------------------------------------------------------------------------
/internal/mocks/user/verifier.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/user"
7 | )
8 |
9 | var _ domain.UserVerifier = (*MockUserVerifier)(nil)
10 |
11 | type MockUserVerifier struct {
12 | VerifyEmailAddressFunc func(ctx context.Context, verificationCode string) error
13 | }
14 |
15 | func (m *MockUserVerifier) VerifyEmailAddress(ctx context.Context, verificationCode string) error {
16 | return m.VerifyEmailAddressFunc(ctx, verificationCode)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/mocks/userconsent/repository.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 |
6 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/userconsent"
7 | "github.com/vigiloauth/vigilo/v2/internal/types"
8 | )
9 |
10 | var _ domain.UserConsentRepository = (*MockUserConsentRepository)(nil)
11 |
12 | type MockUserConsentRepository struct {
13 | HasConsentFunc func(ctx context.Context, userID, clientID string, scope types.Scope) (bool, error)
14 | SaveConsentFunc func(ctx context.Context, userID, clientID string, scope types.Scope) error
15 | RevokeConsentFunc func(ctx context.Context, userID, clientID string) error
16 | }
17 |
18 | func (m *MockUserConsentRepository) HasConsent(ctx context.Context, userID, clientID string, scope types.Scope) (bool, error) {
19 | return m.HasConsentFunc(ctx, userID, clientID, scope)
20 | }
21 |
22 | func (m *MockUserConsentRepository) SaveConsent(ctx context.Context, userID, clientID string, scope types.Scope) error {
23 | return m.SaveConsentFunc(ctx, userID, clientID, scope)
24 | }
25 |
26 | func (m *MockUserConsentRepository) RevokeConsent(ctx context.Context, userID, clientID string) error {
27 | return m.RevokeConsentFunc(ctx, userID, clientID)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/mocks/userconsent/service.go:
--------------------------------------------------------------------------------
1 | package mocks
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | user "github.com/vigiloauth/vigilo/v2/internal/domain/userconsent"
8 | "github.com/vigiloauth/vigilo/v2/internal/types"
9 | )
10 |
11 | var _ user.UserConsentService = (*MockUserConsentService)(nil)
12 |
13 | type MockUserConsentService struct {
14 | CheckUserConsentFunc func(ctx context.Context, userID, clientID string, scope types.Scope) (bool, error)
15 | SaveUserConsentFunc func(ctx context.Context, userID, clientID string, scope types.Scope) error
16 | RevokeConsentFunc func(ctx context.Context, userID, clientID string) error
17 | GetConsentDetailsFunc func(userID, clientID, redirectURI, state string, scope types.Scope, responseType, nonce, display string, r *http.Request) (*user.UserConsentResponse, error)
18 | ProcessUserConsentFunc func(userID, clientID, redirectURI string, scope types.Scope, consentRequest *user.UserConsentRequest, r *http.Request) (*user.UserConsentResponse, error)
19 | }
20 |
21 | func (m *MockUserConsentService) GetConsentDetails(userID, clientID, redirectURI, state string, scope types.Scope, responseType, nonce, display string, r *http.Request) (*user.UserConsentResponse, error) {
22 | return m.GetConsentDetailsFunc(userID, clientID, redirectURI, state, scope, responseType, nonce, display, r)
23 | }
24 |
25 | func (m *MockUserConsentService) ProcessUserConsent(userID, clientID, redirectURI string, scope types.Scope, consentRequest *user.UserConsentRequest, r *http.Request) (*user.UserConsentResponse, error) {
26 | return m.ProcessUserConsentFunc(userID, clientID, redirectURI, scope, consentRequest, r)
27 | }
28 |
29 | func (m *MockUserConsentService) CheckUserConsent(ctx context.Context, userID, clientID string, scope types.Scope) (bool, error) {
30 | return m.CheckUserConsentFunc(ctx, userID, clientID, scope)
31 | }
32 |
33 | func (m *MockUserConsentService) SaveUserConsent(ctx context.Context, userID, clientID string, scope types.Scope) error {
34 | return m.SaveUserConsentFunc(ctx, userID, clientID, scope)
35 | }
36 |
37 | func (m *MockUserConsentService) RevokeConsent(ctx context.Context, userID, clientID string) error {
38 | return m.RevokeConsentFunc(ctx, userID, clientID)
39 | }
40 |
--------------------------------------------------------------------------------
/internal/repository/token/memory_test.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/golang-jwt/jwt"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/token"
12 | )
13 |
14 | const (
15 | testToken string = "test-token"
16 | testID string = "test-id"
17 | )
18 |
19 | func TestTokenStore_AddToken(t *testing.T) {
20 | tokenStore := GetInMemoryTokenRepository()
21 | expiration := time.Now().Add(1 * time.Hour)
22 |
23 | _ = tokenStore.SaveToken(context.Background(), testToken, testID, &domain.TokenData{}, expiration)
24 |
25 | tokenStore.mu.Lock()
26 | defer tokenStore.mu.Unlock()
27 | assert.Contains(t, tokenStore.tokens, testToken)
28 | }
29 |
30 | func TestTokenStore_IsTokenBlacklisted(t *testing.T) {
31 | ctx := context.Background()
32 | tokenStore := GetInMemoryTokenRepository()
33 | expiration := time.Now().Add(-1 * time.Hour)
34 |
35 | tokenData := &domain.TokenData{
36 | TokenClaims: &domain.TokenClaims{
37 | StandardClaims: &jwt.StandardClaims{
38 | ExpiresAt: expiration.Unix(),
39 | },
40 | },
41 | }
42 |
43 | _ = tokenStore.SaveToken(ctx, testToken, testID, tokenData, expiration)
44 | isBlacklisted, err := tokenStore.IsTokenBlacklisted(ctx, testToken)
45 |
46 | require.NoError(t, err)
47 | assert.True(t, isBlacklisted)
48 | }
49 |
50 | func TestTokenStore_DeleteToken(t *testing.T) {
51 | ctx := context.Background()
52 | tokenStore := GetInMemoryTokenRepository()
53 | expiration := time.Now().Add(1 * time.Hour)
54 |
55 | err := tokenStore.SaveToken(ctx, testToken, testID, &domain.TokenData{}, expiration)
56 | require.NoError(t, err)
57 |
58 | token, err := tokenStore.GetToken(ctx, testToken)
59 | require.NoError(t, err)
60 | assert.NotNil(t, token)
61 |
62 | err = tokenStore.DeleteToken(ctx, testToken)
63 | require.NoError(t, err)
64 |
65 | retrievedToken, err := tokenStore.GetToken(ctx, testToken)
66 | require.Error(t, err)
67 | assert.Nil(t, retrievedToken)
68 | }
69 |
--------------------------------------------------------------------------------
/internal/routes/route_group.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import "net/http"
4 |
5 | type RouteGroup struct {
6 | Name string
7 | Middleware []func(http.Handler) http.Handler
8 | Routes []Route
9 | }
10 |
11 | type Route struct {
12 | Methods []string
13 | Pattern string
14 | Handler http.HandlerFunc
15 | Middleware []func(http.Handler) http.Handler
16 | Description string
17 | }
18 |
19 | type RouteBuilder struct {
20 | route Route
21 | }
22 |
23 | func NewRoute() *RouteBuilder {
24 | return &RouteBuilder{
25 | route: Route{
26 | Middleware: make([]func(http.Handler) http.Handler, 0),
27 | },
28 | }
29 | }
30 |
31 | func (rb *RouteBuilder) SetMethods(methods ...string) *RouteBuilder {
32 | rb.route.Methods = methods
33 | return rb
34 | }
35 |
36 | func (rb *RouteBuilder) SetPattern(pattern string) *RouteBuilder {
37 | rb.route.Pattern = pattern
38 | return rb
39 | }
40 |
41 | func (rb *RouteBuilder) SetHandler(handler http.HandlerFunc) *RouteBuilder {
42 | rb.route.Handler = handler
43 | return rb
44 | }
45 |
46 | func (rb *RouteBuilder) SetMiddleware(middleware ...func(http.Handler) http.Handler) *RouteBuilder {
47 | rb.route.Middleware = append(rb.route.Middleware, middleware...)
48 | return rb
49 | }
50 |
51 | func (rb *RouteBuilder) SetDescription(description string) *RouteBuilder {
52 | rb.route.Description = description
53 | return rb
54 | }
55 |
56 | func (rb *RouteBuilder) Build() Route {
57 | return rb.route
58 | }
59 |
60 | func (r Route) getHTTPMethods() []string {
61 | if len(r.Methods) > 0 {
62 | return r.Methods
63 | }
64 |
65 | return []string{http.MethodGet} // Default to GET if no method is provided
66 | }
67 |
--------------------------------------------------------------------------------
/internal/service/authzcode/issuer.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/authzcode"
8 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
9 | "github.com/vigiloauth/vigilo/v2/internal/errors"
10 | "github.com/vigiloauth/vigilo/v2/internal/utils"
11 | )
12 |
13 | var _ domain.AuthorizationCodeIssuer = (*authorizationCodeIssuer)(nil)
14 |
15 | type authorizationCodeIssuer struct {
16 | creator domain.AuthorizationCodeCreator
17 | logger *config.Logger
18 | module string
19 | }
20 |
21 | func NewAuthorizationCodeIssuer(
22 | creator domain.AuthorizationCodeCreator,
23 | ) domain.AuthorizationCodeIssuer {
24 | return &authorizationCodeIssuer{
25 | creator: creator,
26 | logger: config.GetServerConfig().Logger(),
27 | module: "Authorization Code Issuer",
28 | }
29 | }
30 |
31 | // IssueAuthorizationCode generates an authorization code for the given client request.
32 | //
33 | // Parameters:
34 | // - ctx Context: The context for managing timeouts and cancellations.
35 | // - req *ClientAuthorizationRequest: The request containing the metadata to generate an authorization code.
36 | //
37 | // Returns:
38 | // - string: The generated authorization code.
39 | // - error: An error if code generation fails.
40 | func (c *authorizationCodeIssuer) IssueAuthorizationCode(
41 | ctx context.Context,
42 | req *client.ClientAuthorizationRequest,
43 | ) (string, error) {
44 | requestID := utils.GetRequestID(ctx)
45 |
46 | code, err := c.creator.GenerateAuthorizationCode(ctx, req)
47 | if err != nil {
48 | c.logger.Error(c.module, requestID, "[IssueAuthorizationCode] Error generating authorization code: %v", err)
49 | return "", errors.Wrap(err, "", "failed to generate authorization code")
50 | }
51 |
52 | return code, nil
53 | }
54 |
--------------------------------------------------------------------------------
/internal/service/authzcode/issuer_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | client "github.com/vigiloauth/vigilo/v2/internal/domain/client"
10 | "github.com/vigiloauth/vigilo/v2/internal/errors"
11 | mocks "github.com/vigiloauth/vigilo/v2/internal/mocks/authzcode"
12 | )
13 |
14 | func TestAuthorizationCodeIssuer_IssueAuthorizationCode(t *testing.T) {
15 | tests := []struct {
16 | name string
17 | wantErr bool
18 | expectedErrorCode string
19 | request *client.ClientAuthorizationRequest
20 | creator *mocks.MockAuthorizationCodeCreator
21 | }{
22 | {
23 | name: "Success",
24 | wantErr: false,
25 | expectedErrorCode: "",
26 | request: createClientAuthorizationRequest(false),
27 | creator: &mocks.MockAuthorizationCodeCreator{
28 | GenerateAuthorizationCodeFunc: func(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error) {
29 | return "test-code", nil
30 | },
31 | },
32 | },
33 | {
34 | name: "Internal error generating authorization code",
35 | wantErr: true,
36 | expectedErrorCode: errors.SystemErrorCodeMap[errors.ErrCodeInternalServerError],
37 | request: createClientAuthorizationRequest(false),
38 | creator: &mocks.MockAuthorizationCodeCreator{
39 | GenerateAuthorizationCodeFunc: func(ctx context.Context, req *client.ClientAuthorizationRequest) (string, error) {
40 | return "", errors.NewInternalServerError("")
41 | },
42 | },
43 | },
44 | }
45 |
46 | for _, test := range tests {
47 | t.Run(test.name, func(t *testing.T) {
48 | ctx := context.Background()
49 | sut := NewAuthorizationCodeIssuer(
50 | test.creator,
51 | )
52 |
53 | code, err := sut.IssueAuthorizationCode(ctx, test.request)
54 |
55 | if test.wantErr {
56 | require.Error(t, err, "Expected an error but got none")
57 | assert.Equal(t, test.expectedErrorCode, errors.SystemErrorCode(err), "Expected error code does not match")
58 | assert.Empty(t, code, "Expected empty code on error")
59 | } else {
60 | require.NoError(t, err, "Expected no error but got one")
61 | assert.NotEmpty(t, code, "Expected a non-empty code")
62 | }
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/internal/service/crypto/cryptographer_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | "github.com/vigiloauth/vigilo/v2/internal/errors"
9 | )
10 |
11 | func TestCryptographer_HashString(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | wantErr bool
15 | expectedErrCode string
16 | plainStr string
17 | }{
18 | {
19 | name: "Valid Hash",
20 | wantErr: false,
21 | expectedErrCode: "",
22 | plainStr: "testString",
23 | },
24 | {
25 | name: "Empty String",
26 | wantErr: true,
27 | expectedErrCode: errors.SystemErrorCodeMap[errors.ErrCodeHashingFailed],
28 | },
29 | }
30 |
31 | for _, test := range tests {
32 | t.Run(test.name, func(t *testing.T) {
33 | sut := NewCryptographer()
34 | hashedStr, err := sut.HashString(test.plainStr)
35 |
36 | if test.wantErr {
37 | require.Error(t, err, "Expected an error but got none")
38 | assert.Equal(t, test.expectedErrCode, errors.SystemErrorCode(err), "Expected error code does not match")
39 | assert.Empty(t, hashedStr, "Expected empty hashed string on error")
40 | } else {
41 | require.NoError(t, err, "Expected no error but got: %v", err)
42 | assert.NotEmpty(t, hashedStr, "Expected non-empty hashed string")
43 | assert.NotEqual(t, test.plainStr, hashedStr, "Hashed string should not match the plain string")
44 | }
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/internal/service/jwt/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 | "crypto/rsa"
6 |
7 | "github.com/golang-jwt/jwt"
8 | "github.com/vigiloauth/vigilo/v2/idp/config"
9 | domain "github.com/vigiloauth/vigilo/v2/internal/domain/jwt"
10 | tokens "github.com/vigiloauth/vigilo/v2/internal/domain/token"
11 | "github.com/vigiloauth/vigilo/v2/internal/errors"
12 | "github.com/vigiloauth/vigilo/v2/internal/utils"
13 | )
14 |
15 | var _ domain.JWTService = (*jwtService)(nil)
16 |
17 | type jwtService struct {
18 | publicKey *rsa.PublicKey
19 | privateKey *rsa.PrivateKey
20 | keyID string
21 |
22 | logger *config.Logger
23 | module string
24 | }
25 |
26 | func NewJWTService() domain.JWTService {
27 | return &jwtService{
28 | publicKey: config.GetServerConfig().TokenConfig().PublicKey(),
29 | privateKey: config.GetServerConfig().TokenConfig().SecretKey(),
30 | keyID: config.GetServerConfig().TokenConfig().KeyID(),
31 | logger: config.GetServerConfig().Logger(),
32 | module: "JWT Service",
33 | }
34 | }
35 |
36 | func (s *jwtService) ParseWithClaims(ctx context.Context, tokenString string) (*tokens.TokenClaims, error) {
37 | requestID := utils.GetRequestID(ctx)
38 |
39 | tokenClaims, err := jwt.ParseWithClaims(tokenString, &tokens.TokenClaims{}, func(token *jwt.Token) (any, error) {
40 | if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
41 | s.logger.Error(s.module, requestID, "[ParseWithClaims]: Unexpected signing method received")
42 | return nil, errors.New(errors.ErrCodeTokenParsing, "unexpected signing method")
43 | }
44 | return s.publicKey, nil
45 | })
46 |
47 | if err != nil {
48 | s.logger.Error(s.module, requestID, "[ParseWithClaims]: An error occurred parsing the token: %v", err)
49 | return nil, errors.Wrap(err, errors.ErrCodeTokenParsing, "failed to parse JWT with claims")
50 | }
51 |
52 | if claims, ok := tokenClaims.Claims.(*tokens.TokenClaims); ok && tokenClaims.Valid {
53 | return claims, nil
54 | }
55 |
56 | return nil, errors.New(errors.ErrCodeInvalidToken, "provided token is invalid")
57 | }
58 |
59 | func (s *jwtService) SignToken(ctx context.Context, claims *tokens.TokenClaims) (string, error) {
60 | jwtClaims := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
61 | jwtClaims.Header["kid"] = s.keyID
62 |
63 | signedStr, err := jwtClaims.SignedString(s.privateKey)
64 | if err != nil {
65 | return "", errors.Wrap(err, errors.ErrCodeTokenSigning, "failed to sign token with claims")
66 | }
67 |
68 | return signedStr, nil
69 | }
70 |
--------------------------------------------------------------------------------
/internal/service/token/parser.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/vigiloauth/vigilo/v2/idp/config"
7 | jwt "github.com/vigiloauth/vigilo/v2/internal/domain/jwt"
8 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
9 | "github.com/vigiloauth/vigilo/v2/internal/errors"
10 | "github.com/vigiloauth/vigilo/v2/internal/utils"
11 | )
12 |
13 | var _ token.TokenParser = (*tokenParser)(nil)
14 |
15 | type tokenParser struct {
16 | jwtService jwt.JWTService
17 | logger *config.Logger
18 | module string
19 | }
20 |
21 | func NewTokenParser(jwtService jwt.JWTService) token.TokenParser {
22 | return &tokenParser{
23 | jwtService: jwtService,
24 | logger: config.GetServerConfig().Logger(),
25 | module: "Token Parser",
26 | }
27 | }
28 |
29 | // ParseToken parses a JWT token string into TokenClaims.
30 | //
31 | // Parameters:
32 | // - ctx ctx.Context: Context for the request, containing the request ID for logging.
33 | // - tokenString string: The JWT token string to parse and validate.
34 | //
35 | // Returns:
36 | // - *token.TokenClaims: The parsed token claims if successful.
37 | // - error: An error if token parsing, decryption, or validation fails.
38 | func (t *tokenParser) ParseToken(ctx context.Context, tokenString string) (*token.TokenClaims, error) {
39 | requestID := utils.GetRequestID(ctx)
40 |
41 | claims, err := t.jwtService.ParseWithClaims(ctx, tokenString)
42 | if err != nil {
43 | t.logger.Error(t.module, requestID, "[ParseToken]: Failed to parse token: %v", err)
44 | return nil, errors.Wrap(err, "", "failed to parse token with claims")
45 | }
46 |
47 | return claims, nil
48 | }
49 |
--------------------------------------------------------------------------------
/internal/service/token/parser_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | "github.com/vigiloauth/vigilo/v2/internal/constants"
10 | token "github.com/vigiloauth/vigilo/v2/internal/domain/token"
11 | "github.com/vigiloauth/vigilo/v2/internal/errors"
12 | mocks "github.com/vigiloauth/vigilo/v2/internal/mocks/jwt"
13 | )
14 |
15 | func TestTokenParser_ParseToken(t *testing.T) {
16 | tests := []struct {
17 | name string
18 | wantErr bool
19 | expectedErrCode string
20 | expectedClaims *token.TokenClaims
21 | jwtService *mocks.MockJWTService
22 | }{
23 | {
24 | name: "Success",
25 | wantErr: false,
26 | expectedErrCode: "",
27 | expectedClaims: getTestTokenClaims(),
28 | jwtService: &mocks.MockJWTService{
29 | ParseWithClaimsFunc: func(ctx context.Context, tokenString string) (*token.TokenClaims, error) {
30 | return getTestTokenClaims(), nil
31 | },
32 | },
33 | },
34 | {
35 | name: "Internal server error is returned when failing to parse claims",
36 | wantErr: true,
37 | expectedErrCode: errors.SystemErrorCodeMap[errors.ErrCodeInternalServerError],
38 | expectedClaims: nil,
39 | jwtService: &mocks.MockJWTService{
40 | ParseWithClaimsFunc: func(ctx context.Context, tokenString string) (*token.TokenClaims, error) {
41 | return nil, errors.NewInternalServerError("")
42 | },
43 | },
44 | },
45 | }
46 |
47 | for _, test := range tests {
48 | t.Run(test.name, func(t *testing.T) {
49 | service := NewTokenParser(test.jwtService)
50 | ctx := context.WithValue(context.Background(), constants.ContextKeyRequestID, requestID)
51 |
52 | claims, err := service.ParseToken(ctx, "token")
53 |
54 | if test.wantErr {
55 | require.Error(t, err, "Expected an error but got none")
56 | assert.Equal(t, test.expectedErrCode, errors.SystemErrorCode(err), "Expected error codes to be equal")
57 | assert.Nil(t, claims, "Expected claims to be nil")
58 | } else {
59 | require.NoError(t, err, "Expected no error but got: %v", err)
60 | assert.NotNil(t, claims, "Expected claims to not be nil")
61 | assert.Equal(t, test.expectedClaims.Scopes, claims.Scopes, "Expected Scopes to be equal")
62 | assert.Equal(t, test.expectedClaims.Nonce, claims.Nonce, "Expected Nonce values to be equal")
63 | }
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/internal/types/client_types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | type ClientType string
4 |
5 | const (
6 | ConfidentialClient ClientType = "confidential"
7 | PublicClient ClientType = "public"
8 | )
9 |
10 | var SupportedClientTypes = map[ClientType]bool{
11 | ConfidentialClient: true,
12 | PublicClient: true,
13 | }
14 |
15 | func (c ClientType) String() string {
16 | return string(c)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/types/code_challenge_methods.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | type CodeChallengeMethod string
4 |
5 | const (
6 | PlainCodeChallengeMethod CodeChallengeMethod = "plain"
7 | SHA256CodeChallengeMethod CodeChallengeMethod = "S256"
8 | )
9 |
10 | var SupportedCodeChallengeMethods = map[CodeChallengeMethod]bool{
11 | PlainCodeChallengeMethod: true,
12 | SHA256CodeChallengeMethod: true,
13 | }
14 |
15 | func (c CodeChallengeMethod) String() string {
16 | return string(c)
17 | }
18 |
--------------------------------------------------------------------------------
/internal/types/token_endpoint_auth_types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // TokenAuthMethod represents supported OAuth 2.0 token endpoint authentication methods.
4 | // These values are typically used in the client authentication process when obtaining an access token.
5 | type TokenAuthMethod string
6 |
7 | const (
8 | // NoTokenAuth indicates that no client authentication is used at the token endpoint.
9 | NoTokenAuth TokenAuthMethod = "none"
10 |
11 | // ClientSecretPostTokenAuth indicates client authentication using HTTP POST parameters (client_id and client_secret in the body).
12 | ClientSecretPostTokenAuth TokenAuthMethod = "client_secret_post"
13 |
14 | // ClientSecretBasicTokenAuth indicates client authentication using HTTP Basic Authentication (client_id and client_secret in the Authorization header).
15 | ClientSecretBasicTokenAuth TokenAuthMethod = "client_secret_basic"
16 | )
17 |
18 | // SupportedTokenEndpointAuthMethods defines the set of supported and recognized token endpoint authentication methods.
19 | // This can be used for validating incoming configuration or requests.
20 | var SupportedTokenEndpointAuthMethods = map[TokenAuthMethod]bool{
21 | NoTokenAuth: true,
22 | ClientSecretBasicTokenAuth: true,
23 | ClientSecretPostTokenAuth: true,
24 | }
25 |
26 | func (t TokenAuthMethod) String() string {
27 | return string(t)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/types/token_types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | type TokenType string
4 |
5 | const (
6 | RefreshTokenType TokenType = "refresh"
7 | AccessTokenType TokenType = "access"
8 | )
9 |
--------------------------------------------------------------------------------
/internal/utils/context.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/vigiloauth/vigilo/v2/internal/constants"
7 | )
8 |
9 | // GetRequestID retrieves the request ID from the context.
10 | //
11 | // Parameters:
12 | // - ctx context.Context: The context containing the request ID.
13 | //
14 | // Returns:
15 | // - string: The request ID as a string if present, otherwise an empty string.
16 | func GetRequestID(ctx context.Context) string {
17 | if requestID, ok := ctx.Value(constants.ContextKeyRequestID).(string); ok {
18 | return requestID
19 | }
20 |
21 | return ""
22 | }
23 |
24 | // GetValueFromContext retrieves a string value from the context based on the provided key.
25 | //
26 | // Parameters:
27 | // - ctx ctx.Context: The context from which to retrieve the value.
28 | // - value constants.ContextKey: The key of type constants.ContextKey used to retrieve the value.
29 | //
30 | // Returns:
31 | // - any: The value if found, otherwise an empty string.
32 | func GetValueFromContext(ctx context.Context, value constants.ContextKey) any {
33 | return ctx.Value(value)
34 | }
35 |
36 | // AddKeyValueToContext returns a new context with the specified key-value pair added.
37 | //
38 | // Parameters:
39 | // - ctx context.Context: The base context.
40 | // - key constants.ContextKey: The key of type constants.ContextKey to associate with the value.
41 | // - value any: The value to store in the context.
42 | //
43 | // Returns:
44 | // - context.Context: A new context with the key-value pair added.
45 | func AddKeyValueToContext(ctx context.Context, key constants.ContextKey, value any) context.Context {
46 | return context.WithValue(ctx, key, value)
47 | }
48 |
--------------------------------------------------------------------------------
/internal/utils/logging.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "net/url"
5 | )
6 |
7 | // TruncateSensitive shortens sensitive strings for safe logging.
8 | //
9 | // Parameters:
10 | // - data string: The sensitive string to truncate.
11 | //
12 | // Returns:
13 | // - string: A truncated version of the string with "[REDACTED]" appended if its length
14 | // is greater than 5. Otherwise, returns the original string.
15 | func TruncateSensitive(data string) string {
16 | const minDataLength int = 5
17 | if len(data) > minDataLength {
18 | return data[:minDataLength] + "[REDACTED]"
19 | }
20 |
21 | return data
22 | }
23 |
24 | // SanitizeURL redacts query parameters from the provided URL for secure logging.
25 | //
26 | // Parameters:
27 | // - uri string: The URL string to sanitize.
28 | //
29 | // Returns:
30 | // - string: The sanitized URL with query parameters replaced by "[REDACTED]".
31 | // If the URL is invalid, returns "[INVALID URL]".
32 | func SanitizeURL(uri string) string {
33 | parsed, err := url.Parse(uri)
34 | if err != nil {
35 | return "[INVALID URL]"
36 | }
37 |
38 | parsed.RawQuery = "[REDACTED]"
39 | return parsed.String()
40 | }
41 |
--------------------------------------------------------------------------------
/internal/web/decode_json.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/vigiloauth/vigilo/v2/internal/errors"
8 | )
9 |
10 | // DecodeJSONRequest decodes the JSON request body into the provided generic type T.
11 | // It reads the request body, attempts to decode it into the specified type, and returns
12 | // a pointer to the decoded object. If decoding fails, it returns an error wrapped with
13 | // an internal server error code.
14 | //
15 | // Parameters:
16 | //
17 | // - w: The HTTP response writer.
18 | // - r: The HTTP request containing the JSON body to decode.
19 | //
20 | // Returns:
21 | //
22 | // - A pointer to the decoded object of type T, or an error if decoding fails.
23 | func DecodeJSONRequest[T any](w http.ResponseWriter, r *http.Request) (*T, error) {
24 | var request T
25 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
26 | return nil, errors.Wrap(err, errors.ErrCodeInternalServerError, "failed to decode request")
27 | }
28 |
29 | return &request, nil
30 | }
31 |
--------------------------------------------------------------------------------
/internal/web/endpoints.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | const (
4 | defaultAuthEndpoint string = "/auth"
5 | defaultClientEndpoint string = "/clients"
6 | defaultOAuthEndpoint string = "/oauth2"
7 | defaultTokenEndpoint string = "/tokens"
8 | defaultAdminEndpoint string = "/admins"
9 | wellKnown string = "/.well-known"
10 | )
11 |
12 | var UserEndpoints = struct {
13 | Registration string
14 | Login string
15 | Logout string
16 | ResetPassword string
17 | Verify string
18 | }{
19 | Registration: defaultAuthEndpoint + "/signup",
20 | Login: defaultAuthEndpoint + "/login",
21 | Logout: defaultAuthEndpoint + "/logout",
22 | ResetPassword: defaultAuthEndpoint + "/reset-password/confirm",
23 | Verify: defaultAuthEndpoint + "/verify",
24 | }
25 |
26 | var ClientEndpoints = struct {
27 | RegenerateSecret string
28 | ClientConfiguration string
29 | Register string
30 | GetClientByID string
31 | }{
32 | Register: defaultClientEndpoint + "/register",
33 | RegenerateSecret: defaultClientEndpoint + "/regenerate-secret",
34 | ClientConfiguration: defaultOAuthEndpoint + defaultClientEndpoint + "/register",
35 | GetClientByID: defaultOAuthEndpoint + defaultClientEndpoint,
36 | }
37 |
38 | var OAuthEndpoints = struct {
39 | Token string
40 | Authorize string
41 | Authenticate string
42 | UserConsent string
43 | TokenExchange string
44 | IntrospectToken string
45 | RevokeToken string
46 | }{
47 | Token: defaultOAuthEndpoint + "/token",
48 | Authorize: defaultOAuthEndpoint + "/authorize",
49 | Authenticate: defaultOAuthEndpoint + "/authenticate",
50 | UserConsent: defaultOAuthEndpoint + "/consent",
51 | TokenExchange: defaultOAuthEndpoint + "/token",
52 | IntrospectToken: defaultOAuthEndpoint + defaultTokenEndpoint + "/introspect",
53 | RevokeToken: defaultOAuthEndpoint + defaultTokenEndpoint + "/revoke",
54 | }
55 |
56 | var AdminEndpoints = struct {
57 | GetAuditEvents string
58 | }{
59 | GetAuditEvents: defaultAdminEndpoint + "/audit-events",
60 | }
61 |
62 | var OIDCEndpoints = struct {
63 | UserInfo string
64 | JWKS string
65 | Discovery string
66 | }{
67 | UserInfo: defaultOAuthEndpoint + "/userinfo",
68 | JWKS: defaultOAuthEndpoint + wellKnown + "/jwks.json",
69 | Discovery: defaultOAuthEndpoint + wellKnown + "/openid-configuration",
70 | }
71 |
--------------------------------------------------------------------------------
/scripts/push_docker_dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script builds the Docker image and pushes it to the repository.
4 |
5 | IMAGE_NAME="vigilo-auth"
6 | TAG_NAME="latest-dev"
7 | DOCKER_REPO="vigiloauth"
8 |
9 | FULL_IMAGE="$DOCKER_REPO/$IMAGE_NAME:$TAG_NAME"
10 |
11 | # Build the Docker image
12 | echo "Building Docker image..."
13 | cd ..
14 | docker build \
15 | --cache-from $FULL_IMAGE \
16 | --build-arg BUILDKIT_INLINE_CACHE=1 \
17 | -t $IMAGE_NAME:$TAG_NAME .
18 |
19 | # Tag the image for the repository
20 | echo "Tagging Docker image as $FULL_IMAGE..."
21 | docker tag $IMAGE_NAME:$TAG_NAME $FULL_IMAGE
22 |
23 | # Login to Docker
24 | echo "Logging in to Docker..."
25 | docker login
26 |
27 | # Push the image
28 | echo "Pushing Docker image to repository..."
29 | docker push $FULL_IMAGE
30 |
--------------------------------------------------------------------------------
/scripts/run_docker_local.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script creates a new docker container
4 | # for local/manual testing purposes.
5 |
6 | set -e # Exit on any error.
7 |
8 | CONTAINER_NAME="vigilo-auth-local"
9 | IMAGE_NAME="vigilo-auth"
10 | TAG_NAME="local"
11 | CONTAINER_PORT=8080
12 |
13 | # Stop all running containers
14 | echo "Stopping all running Docker containers..."
15 | docker stop $(docker ps -q) 2>/dev/null || true
16 |
17 | # Remove all containers (not just running ones)
18 | echo "Removing all Docker containers..."
19 | docker rm $(docker ps -a -q) 2>/dev/null || true
20 |
21 | # Build the Docker image
22 | echo "Building Docker image..."
23 | cd ..
24 | docker build -t $IMAGE_NAME:$TAG_NAME .
25 |
26 | # Run the container
27 | echo "Starting local container..."
28 | docker run -p $CONTAINER_PORT:$CONTAINER_PORT --name $CONTAINER_NAME \
29 | --env-file .env \
30 | $IMAGE_NAME:$TAG_NAME
--------------------------------------------------------------------------------
/scripts/update_imports.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script updates all Go import paths in the project to reflect a new major version.
4 | # Specifically, it replaces occurrences of "github.com/vigiloauth/vigilo/{CURRENT_VERSION}" with
5 | # "github.com/vigiloauth/vigilo/{NEW_VERSION}" in all .go files within the project.
6 |
7 | CURRENT_VERSION=v2
8 | SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
9 | PROJECT_ROOT="$SCRIPT_DIR/.."
10 | IMPORT_PATH=github.com/vigiloauth/vigilo
11 |
12 | while true; do
13 | read -p "Enter the new version (e.g., 4 for v4): " NEW_VERSION
14 |
15 | if [[ -z "$NEW_VERSION" ]]; then
16 | echo -e "Error: Version cannot be empty. Please try again.\n\n"
17 | continue
18 | fi
19 |
20 | if ! [[ "$NEW_VERSION" =~ ^[0-9]+$ ]]; then
21 | echo -e "Error: Version must be a number. Please try again.\n\n"
22 | continue
23 | fi
24 |
25 | NEW_VERSION="v$NEW_VERSION"
26 | break
27 | done
28 |
29 | # Navigate to the root of the project
30 | cd "$PROJECT_ROOT" || exit 1
31 |
32 | # Find all .go files and update imports
33 | find "$PROJECT_ROOT" \
34 | -type f \
35 | -name "*.go" \
36 | -exec sed -i '' "s|$IMPORT_PATH/$CURRENT_VERSION|$IMPORT_PATH/$NEW_VERSION|g" {} +
37 |
38 | echo "Import paths updated from $CURRENT_VERSION to $NEW_VERSION successfully."
--------------------------------------------------------------------------------
/tests/integration/admin_handler_integration_test.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "github.com/vigiloauth/vigilo/v2/internal/constants"
11 | "github.com/vigiloauth/vigilo/v2/internal/web"
12 | )
13 |
14 | func TestAdminHandler_GetAuditEvents(t *testing.T) {
15 | t.Run("Success", func(t *testing.T) {
16 | testContext := NewVigiloTestContext(t)
17 | defer testContext.TearDown()
18 |
19 | testContext.WithAdminToken(testUserID, time.Duration(2*time.Hour))
20 | testContext.WithAuditEvents()
21 | headers := map[string]string{"Authorization": constants.BearerAuthHeader + testContext.JWTToken}
22 |
23 | from := time.Now().Add(-5 * time.Hour).UTC().Format(time.RFC3339)
24 | to := time.Now().UTC().Format(time.RFC3339)
25 |
26 | queryParams := url.Values{}
27 | queryParams.Add("from", from)
28 | queryParams.Add("to", to)
29 | queryParams.Add("UserID", testUserID)
30 | queryParams.Add("EventType", "login_attempt")
31 | queryParams.Add("Success", "false")
32 | queryParams.Add("IP", testIP)
33 | queryParams.Add("limit", "50")
34 | queryParams.Add("offset", "0")
35 | endpoint := web.AdminEndpoints.GetAuditEvents + "?" + queryParams.Encode()
36 |
37 | rr := testContext.SendHTTPRequest(http.MethodGet, endpoint, nil, headers)
38 |
39 | assert.Equal(t, http.StatusOK, rr.Code)
40 | })
41 |
42 | t.Run("Error is returned when user does not have the required roles", func(t *testing.T) {
43 | testContext := NewVigiloTestContext(t)
44 | defer testContext.TearDown()
45 |
46 | testContext.WithUser([]string{constants.UserRole})
47 | testContext.WithJWTToken(testUserID, time.Duration(2*time.Hour))
48 | testContext.WithAuditEvents()
49 | headers := map[string]string{"Authorization": constants.BearerAuthHeader + testContext.JWTToken}
50 |
51 | from := time.Now().Add(-5 * time.Hour).UTC().Format(time.RFC3339)
52 | to := time.Now().UTC().Format(time.RFC3339)
53 |
54 | queryParams := url.Values{}
55 | queryParams.Add("from", from)
56 | queryParams.Add("to", to)
57 | queryParams.Add("UserID", testUserID)
58 | queryParams.Add("EventType", "login_attempt")
59 | queryParams.Add("Success", "false")
60 | queryParams.Add("IP", testIP)
61 | queryParams.Add("limit", "50")
62 | queryParams.Add("offset", "0")
63 | endpoint := web.AdminEndpoints.GetAuditEvents + "?" + queryParams.Encode()
64 |
65 | rr := testContext.SendHTTPRequest(http.MethodGet, endpoint, nil, headers)
66 |
67 | assert.Equal(t, http.StatusForbidden, rr.Code)
68 | })
69 | }
70 |
--------------------------------------------------------------------------------
/tests/integration/rate_limiting_test.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | "github.com/vigiloauth/vigilo/v2/idp/config"
12 | "github.com/vigiloauth/vigilo/v2/internal/constants"
13 | users "github.com/vigiloauth/vigilo/v2/internal/domain/user"
14 | "github.com/vigiloauth/vigilo/v2/internal/web"
15 | )
16 |
17 | func TestRateLimiting(t *testing.T) {
18 | testContext := NewVigiloTestContext(t)
19 | defer testContext.TearDown()
20 |
21 | user := users.NewUser("", testEmail, testPassword1)
22 | user.ID = testUserID
23 |
24 | request := users.UserLoginRequest{Username: testUsername, Password: testPassword1}
25 | requestBody, err := json.Marshal(request)
26 | require.NoError(t, err, "failed to marshal request body")
27 |
28 | requestsPerMinute := 5
29 | testContext.WithCustomConfig(config.WithMaxRequestsPerMinute(requestsPerMinute))
30 |
31 | for range requestsPerMinute {
32 | testContext.WithUser([]string{constants.AdminRole})
33 | rr := testContext.SendHTTPRequest(
34 | http.MethodPost,
35 | web.UserEndpoints.Login,
36 | bytes.NewBuffer(requestBody),
37 | nil,
38 | )
39 |
40 | assert.Equal(t, http.StatusOK, rr.Code)
41 | testContext.ClearSession()
42 | }
43 |
44 | rr := testContext.SendHTTPRequest(
45 | http.MethodPost,
46 | web.UserEndpoints.Login,
47 | bytes.NewBuffer(requestBody),
48 | nil,
49 | )
50 |
51 | assert.Equal(t, http.StatusOK, rr.Code)
52 | }
53 |
--------------------------------------------------------------------------------
/ui/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import globals from "globals";
3 | import pluginReact from "eslint-plugin-react";
4 | import { defineConfig } from "eslint/config";
5 |
6 |
7 | export default defineConfig([
8 | { files: ["**/*.{js,mjs,cjs,jsx}"], plugins: { js }, extends: ["js/recommended"] },
9 | { files: ["**/*.{js,mjs,cjs,jsx}"], languageOptions: { globals: globals.browser } },
10 | pluginReact.configs.flat.recommended,
11 | ]);
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-server-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^6.0.0",
7 | "antd": "^5.25.1",
8 | "lucide-react": "^0.510.0",
9 | "react": "^18.2.0",
10 | "react-dom": "^18.2.0",
11 | "react-router-dom": "^7.5.3",
12 | "react-scripts": "5.0.1"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject",
19 | "lint": "eslint ."
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | },
38 | "devDependencies": {
39 | "@eslint/js": "^9.26.0",
40 | "@types/react": "^19.1.3",
41 | "@types/react-dom": "^19.1.3",
42 | "autoprefixer": "^10.4.14",
43 | "eslint": "^8.57.1",
44 | "eslint-config-airbnb": "^19.0.4",
45 | "eslint-config-prettier": "^10.1.3",
46 | "eslint-plugin-jsx-a11y": "^6.10.2",
47 | "eslint-plugin-prettier": "^5.4.0",
48 | "eslint-plugin-react": "^7.37.5",
49 | "globals": "^16.1.0",
50 | "postcss": "^8.4.27",
51 | "prettier": "^3.5.3",
52 | "prettier-eslint": "^16.4.2",
53 | "sass": "^1.87.0",
54 | "sass-loader": "^16.0.5",
55 | "tailwindcss": "^3.3.3"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 | VigiloAuth
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from "react-router-dom";
2 | import "./index.css";
3 | import AuthenticationPage from "./pages/AuthenticationPage";
4 | import ConsentPage from "./pages/ConsentPage";
5 | import { ApplicationContextProvider } from "./context/ApplicationContext";
6 | import URL_PARAMS from "./constants/urlParams";
7 | import ErrorPage from "./pages/ErrorPage";
8 |
9 | function App() {
10 | const queryParams = new URLSearchParams(window.location.search);
11 | const display = queryParams.get(URL_PARAMS.DISPLAY);
12 | const errorType = queryParams.get(URL_PARAMS.TYPE);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | }
22 | />
23 | }
26 | />
27 | }
30 | />
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/ui/src/api/clientApi.js:
--------------------------------------------------------------------------------
1 | import ENDPOINT from "../constants/endpoints";
2 |
3 | export async function getClientByID({ clientID }) {
4 | const endpoint = `${ENDPOINT.GET_CLIENT_BY_ID}/${clientID}`;
5 | try {
6 | const response = await fetch(endpoint, {
7 | method: "GET",
8 | });
9 | return await response.json();
10 | } catch (err) {
11 | throw new Error("Something went wrong retrieving the client's logo.");
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/components/BaseButton.jsx:
--------------------------------------------------------------------------------
1 | import { Form, Button } from "antd";
2 | import React from "react";
3 | import PropTypes from "prop-types";
4 |
5 | const BaseButton = ({
6 | type = "primary",
7 | onClick,
8 | loading,
9 | children,
10 | style = {},
11 | block = true,
12 | disabled = false,
13 | size = "default",
14 | }) => {
15 | return (
16 |
17 |
28 |
29 | );
30 | };
31 |
32 | BaseButton.propTypes = {
33 | type: PropTypes.oneOf(["primary", "link", "text", "default", "dashed"]),
34 | onClick: PropTypes.func,
35 | loading: PropTypes.bool,
36 | children: PropTypes.node.isRequired,
37 | style: PropTypes.object,
38 | block: PropTypes.bool,
39 | disabled: PropTypes.bool,
40 | size: PropTypes.oneOf(["large", "default", "small"]),
41 | };
42 |
43 | export default BaseButton;
44 |
--------------------------------------------------------------------------------
/ui/src/components/Container.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | /**
5 | * Container
6 | *
7 | * A generic container component that applies customizable styles,
8 | * including width and maxWidth, to its children.
9 | *
10 | * @component
11 | * @example
12 | *
13 | * Hello World
14 | *
15 | *
16 | * @param {object} props - The props for the component.
17 | * @param {React.ReactNode} props.children - The content to render inside the container.
18 | * @param {string} [props.width="100%"] - CSS width of the container.
19 | * @param {string} [props.maxWidth="350px"] - CSS maxWidth of the container.
20 | * @param {object} [props.style={}] - Additional CSS styles to apply.
21 | *
22 | * @returns {JSX.Element} The styled container element.
23 | */
24 | const Container = ({
25 | children,
26 | width = "100%",
27 | maxWidth = "350px",
28 | maxHeight = "600px",
29 | style = {},
30 | layout = "vertical",
31 | className,
32 | }) => {
33 | return (
34 |
44 | {children}
45 |
46 | );
47 | };
48 |
49 | Container.propTypes = {
50 | children: PropTypes.node.isRequired,
51 | width: PropTypes.string,
52 | maxWidth: PropTypes.string,
53 | style: PropTypes.object,
54 | layout: PropTypes.string,
55 | className: PropTypes.string,
56 | };
57 |
58 | export default Container;
59 |
--------------------------------------------------------------------------------
/ui/src/components/FlexContainer.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { Flex } from "antd";
3 |
4 | const FlexContainer = ({
5 | children,
6 | vertical = false,
7 | justify = "center",
8 | align = "center",
9 | width,
10 | height,
11 | className,
12 | style = {},
13 | }) => {
14 | return (
15 |
22 | {children}
23 |
24 | );
25 | };
26 |
27 | FlexContainer.propTypes = {
28 | children: PropTypes.node.isRequired,
29 | vertical: PropTypes.bool,
30 | justify: PropTypes.oneOf([
31 | "start",
32 | "center",
33 | "end",
34 | "space-around",
35 | "space-between",
36 | "space-evenly",
37 | ]),
38 | align: PropTypes.oneOf(["start", "center", "end", "stretch", "baseline"]),
39 | style: PropTypes.object,
40 | height: PropTypes.string,
41 | className: PropTypes.string,
42 | };
43 |
44 | export default FlexContainer;
45 |
--------------------------------------------------------------------------------
/ui/src/components/FormInput.jsx:
--------------------------------------------------------------------------------
1 | import { Form, Input } from "antd";
2 | import React from "react";
3 | import PropTypes from "prop-types";
4 |
5 | /**
6 | * A reusable form input component that supports both text and password inputs.
7 | *
8 | * @component
9 | * @param {Object} props - The props object.
10 | * @param {string} props.label - The label for the form input.
11 | * @param {string} props.name - The name of the form field (used for form data binding).
12 | * @param {boolean} props.required - Whether the field is required.
13 | * @param {string} props.message - Validation message shown when the field is invalid.
14 | * @param {string} props.value - The current value of the input.
15 | * @param {function} props.onChange - Callback function triggered when the input value changes.
16 | * @param {React.ReactNode} props.icon - Optional icon to display in the input prefix.
17 | * @param {boolean} [props.isPassword=false] - If true, renders a password input field.
18 | *
19 | * @returns {JSX.Element} A Form.Item with an Input or Password Input inside.
20 | */
21 | const FormInput = ({
22 | label,
23 | placeholder,
24 | name,
25 | required,
26 | message,
27 | value,
28 | onChange,
29 | icon,
30 | isPassword = false,
31 | validateStatus = "",
32 | size = "middle",
33 | style = {},
34 | className,
35 | ...props
36 | }) => {
37 | const InputComponent = isPassword ? Input.Password : Input;
38 | return (
39 |
46 |
55 |
56 | );
57 | };
58 |
59 | FormInput.propTypes = {
60 | label: PropTypes.string,
61 | name: PropTypes.string.isRequired,
62 | required: PropTypes.bool,
63 | message: PropTypes.string,
64 | value: PropTypes.string,
65 | onChange: PropTypes.func,
66 | icon: PropTypes.node,
67 | isPassword: PropTypes.bool,
68 | validateStatus: PropTypes.string,
69 | placeholder: PropTypes.string,
70 | size: PropTypes.oneOf(["large", "middle", "small"]),
71 | className: PropTypes.string,
72 | };
73 |
74 | export default FormInput;
75 |
--------------------------------------------------------------------------------
/ui/src/constants/endpoints.js:
--------------------------------------------------------------------------------
1 | const ENDPOINT = {
2 | USER_AUTH: "/identity/oauth2/authenticate",
3 | USER_CONSENT: "/identity/oauth2/consent",
4 | GET_CLIENT_BY_ID: "/identity/oauth2/clients",
5 | };
6 |
7 | export default ENDPOINT;
8 |
--------------------------------------------------------------------------------
/ui/src/constants/urlParams.js:
--------------------------------------------------------------------------------
1 | const URL_PARAMS = {
2 | CLIENT_ID: "client_id",
3 | REDIRECT_URI: "redirect_uri",
4 | STATE: "state",
5 | SCOPE: "scope",
6 | RESPONSE_TYPE: "response_type",
7 | NONCE: "nonce",
8 | DISPLAY: "display",
9 | TYPE: "type",
10 | ACR: "acr_values",
11 | CLAIMS: "claims",
12 | };
13 |
14 | export default URL_PARAMS;
15 |
--------------------------------------------------------------------------------
/ui/src/context/ApplicationContext.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState, useEffect } from "react";
2 | import { getClientByID } from "../api/clientApi";
3 |
4 | import URL_PARAMS from "../constants/urlParams";
5 |
6 | const ApplicationContext = createContext(null);
7 |
8 | export const ApplicationContextProvider = ({ children }) => {
9 | const queryParams = new URLSearchParams(window.location.search);
10 |
11 | const [queryValues, setQueryValues] = useState({
12 | clientID: queryParams.get(URL_PARAMS.CLIENT_ID),
13 | redirectURI: queryParams.get(URL_PARAMS.REDIRECT_URI),
14 | state: queryParams.get(URL_PARAMS.STATE) || "",
15 | scope: queryParams.get(URL_PARAMS.SCOPE) || "",
16 | responseType: queryParams.get(URL_PARAMS.RESPONSE_TYPE) || "",
17 | nonce: queryParams.get(URL_PARAMS.NONCE) || "",
18 | display: queryParams.get(URL_PARAMS.DISPLAY) || "",
19 | acrValues: queryParams.get(URL_PARAMS.ACR) || "",
20 | claims: queryParams.get(URL_PARAMS.CLAIMS) || "",
21 | });
22 |
23 | const [clientInfo, setClientInfo] = useState(null);
24 | const [loading, setLoading] = useState(true);
25 |
26 | useEffect(() => {
27 | if (!queryValues.clientID) return;
28 | const fetchClientInfo = async () => {
29 | // try {
30 | // setClientInfo({
31 | // logo_uri: "https://www.certification.openid.net/images/openid.png",
32 | // policy_uri: "https://www.certification.openid.net/login.html",
33 | // });
34 | // } catch (err) {
35 | // console.log(err);
36 | // } finally {
37 | // setLoading(false);
38 | // }
39 | try {
40 | const data = await getClientByID({ clientID: queryValues.clientID });
41 | setClientInfo(data);
42 | } catch (err) {
43 | console.error("Error fetching client info:", err);
44 | } finally {
45 | setLoading(false);
46 | }
47 | };
48 |
49 | fetchClientInfo();
50 | }, [queryValues.clientID]);
51 |
52 | return (
53 |
56 | {children}
57 |
58 | );
59 | };
60 |
61 | export const useApplicationContext = () => useContext(ApplicationContext);
62 |
--------------------------------------------------------------------------------
/ui/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background-color: #f5f5f5;
7 | }
8 |
--------------------------------------------------------------------------------
/ui/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render(
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/ui/src/pages/AuthenticationPage.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { Card, Typography, Spin } from "antd";
3 | import FlexContainer from "../components/FlexContainer";
4 | import Container from "../components/Container";
5 | import AuthenticationForm from "../forms/AuthenticationForm";
6 | import AuthenticationPopup from "../popups/AuthenticationPopup";
7 | import { useApplicationContext } from "../context/ApplicationContext";
8 | import "../styles/AuthenticationPage.scss";
9 |
10 | const { Title, Text } = Typography;
11 |
12 | export default function AuthenticationPage({ display }) {
13 | const [clientLogo, setClientLogo] = useState(null);
14 | const [clientName, setClientName] = useState("");
15 | const [imageLoaded, setImageLoaded] = useState(false);
16 | const { clientInfo, loading: contextLoading } = useApplicationContext();
17 |
18 | const defaultSignInMessage = "Please sign in to continue";
19 | const signInMessage = `You are signing into ${clientName}`;
20 |
21 | useEffect(() => {
22 | if (!clientInfo) return;
23 | setClientName(clientInfo.name || "");
24 |
25 | if (!clientInfo.logo_uri) return;
26 | const img = new Image();
27 | img.src = clientInfo.logo_uri;
28 | img.onload = () => {
29 | setClientLogo(
);
30 | setImageLoaded(true);
31 | };
32 | img.onerror = () => {
33 | setClientLogo(null);
34 | setImageLoaded(true);
35 | };
36 | }, [clientInfo]);
37 |
38 | const isLoading = contextLoading || (clientInfo?.logo_uri && !imageLoaded);
39 |
40 | if (isLoading) {
41 | return (
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | return display === "popup" ? (
49 |
50 | ) : (
51 |
52 |
53 |
54 | {clientLogo ? clientLogo : Welcome Back}
55 |
56 | {clientName ? signInMessage : defaultSignInMessage}
57 |
58 |
59 |
60 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/ui/src/pages/ConsentPage.jsx:
--------------------------------------------------------------------------------
1 | import { Card } from "antd";
2 | import FlexContainer from "../components/FlexContainer";
3 | import ConsentForm from "../forms/ConsentForm";
4 | import ConsentPopup from "../popups/ConsentPopup";
5 | import "../styles/ConsentPage.scss";
6 | import { useApplicationContext } from "../context/ApplicationContext";
7 | import { useEffect, useState } from "react";
8 |
9 | export default function ConsentPage({ display }) {
10 | const [policyURI, setPolicyURI] = useState("");
11 | const { clientInfo } = useApplicationContext();
12 |
13 | useEffect(() => {
14 | if (!clientInfo || !clientInfo.policy_uri) return;
15 | setPolicyURI(clientInfo.policy_uri);
16 | }, [clientInfo]);
17 |
18 | return display === "popup" ? (
19 |
20 | ) : (
21 |
22 |
23 | {}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/ui/src/pages/ErrorPage.jsx:
--------------------------------------------------------------------------------
1 | import InvalidRedirectErrorPage from "../forms/InvalidRedirectErrorPage";
2 |
3 | export default function ErrorPage({ errorType }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/src/popups/AuthenticationPopup.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Modal, Typography, Spin } from "antd";
3 | import { useApplicationContext } from "../context/ApplicationContext";
4 | import Container from "../components/Container";
5 | import AuthenticationForm from "../forms/AuthenticationForm";
6 | import FlexContainer from "../components/FlexContainer";
7 | import "../styles/popup.scss";
8 |
9 | const { Title, Text } = Typography;
10 |
11 | export default function AuthenticationPopup({
12 | clientLogo,
13 | clientName,
14 | policyURI,
15 | loading,
16 | }) {
17 | const [visible, setVisible] = useState(true);
18 | const { redirectURI } = useApplicationContext();
19 |
20 | const onCancel = () => {
21 | setVisible(false);
22 | };
23 |
24 | const afterClose = () => {
25 | if (redirectURI) {
26 | window.location.replace(redirectURI);
27 | }
28 | };
29 |
30 | const defaultSignInMessage = "Please sign in to continue";
31 | const signInMessage = `You are signing into ${clientName}`;
32 |
33 | if (loading) {
34 | return (
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | return (
42 |
52 |
53 | {clientLogo ? clientLogo : Welcome Back}
54 |
55 | {clientName ? signInMessage : defaultSignInMessage}
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/ui/src/popups/ConsentPopup.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Modal } from "antd";
3 | import { useApplicationContext } from "../context/ApplicationContext";
4 | import ConsentForm from "../forms/ConsentForm";
5 | import "../styles/popup.scss";
6 |
7 | export default function ConsentPopup({ policyURI }) {
8 | const [visible, setVisible] = useState(true);
9 | const { redirectURI } = useApplicationContext();
10 |
11 | const onCancel = () => {
12 | setVisible(false);
13 | };
14 |
15 | const afterClose = () => {
16 | if (redirectURI) {
17 | window.location.replace(redirectURI);
18 | }
19 | };
20 |
21 | return (
22 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/ui/src/styles/AuthenticationPage.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #1890ff;
2 | $secondary-color: #001529;
3 | $text-color: rgba(0, 0, 0, 0.85);
4 | $text-color-secondary: rgba(0, 0, 0, 0.45);
5 | $border-radius: 8px;
6 | $box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
7 | $font-family:
8 | -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
9 | Arial, sans-serif;
10 | $mobile-breakpoint: 768px;
11 |
12 | .auth-container {
13 | overflow: hidden;
14 | height: 100vh;
15 | background-color: #f5f5f5;
16 | padding: 40px 16px;
17 | @media (max-width: $mobile-breakpoint) {
18 | padding: 0;
19 | }
20 | }
21 |
22 | .auth-card {
23 | box-shadow: $box-shadow;
24 | border-radius: $border-radius;
25 | width: 100%;
26 | max-width: 450px;
27 | min-height: auto;
28 | padding: 32px;
29 | background-color: #fff;
30 |
31 | @media (max-width: $mobile-breakpoint) {
32 | border-radius: 0;
33 | box-shadow: none;
34 | max-width: 100%;
35 | min-height: 100vh;
36 | margin: 0;
37 | padding: 24px 16px;
38 | display: flex;
39 | flex-direction: column;
40 | justify-content: center;
41 | }
42 | }
43 |
44 | .auth-header {
45 | text-align: center;
46 | margin-bottom: 32px;
47 | user-select: none;
48 |
49 | @media (max-width: $mobile-breakpoint) {
50 | margin-bottom: 24px;
51 | }
52 |
53 | h2,
54 | h3 {
55 | margin-bottom: 8px;
56 | color: $text-color;
57 | font-weight: 600;
58 | }
59 |
60 | .subtitle {
61 | color: $text-color-secondary;
62 | }
63 | }
64 |
65 | @keyframes fadeIn {
66 | from {
67 | opacity: 0;
68 | transform: translateY(10px);
69 | }
70 | to {
71 | opacity: 1;
72 | transform: translateY(0);
73 | }
74 | }
75 |
76 | .auth-card {
77 | user-select: none;
78 | animation: fadeIn 0.4s ease-in-out;
79 | }
80 |
--------------------------------------------------------------------------------
/ui/src/styles/ConsentPage.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #1890ff;
2 | $background-color: #f5f5f5;
3 | $card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
4 | $mobile-breakpoint: 768px;
5 |
6 | .consent-form-container {
7 | min-height: 100vh;
8 | background-color: $background-color;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 |
13 | @media (min-width: $mobile-breakpoint) {
14 | padding: 40px 16px;
15 | }
16 |
17 | @media (max-width: $mobile-breakpoint) {
18 | padding: 0;
19 | }
20 |
21 | .title-container {
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | margin-bottom: 24px;
26 | margin-left: auto;
27 | margin-right: auto;
28 | text-align: center;
29 | }
30 |
31 | .card-title {
32 | @media (max-width: $mobile-breakpoint) {
33 | font-size: 1.5em !important;
34 | }
35 |
36 | @media (min-width: $mobile-breakpoint) {
37 | font-size: 1.8em !important;
38 | }
39 |
40 | user-select: none;
41 | }
42 | }
43 |
44 | .consent-card {
45 | width: 100%;
46 | display: flex;
47 | flex-direction: column;
48 |
49 | @media (min-width: $mobile-breakpoint) {
50 | border-radius: 8px;
51 | box-shadow: $card-shadow;
52 | max-width: 550px;
53 | padding: 32px;
54 | margin: auto;
55 | min-height: auto;
56 | justify-content: flex-start;
57 | }
58 |
59 | @media (max-width: $mobile-breakpoint) {
60 | border-radius: 0;
61 | box-shadow: none;
62 | max-width: 100%;
63 | height: 100vh;
64 | padding: 24px 16px;
65 | margin: 0;
66 | justify-content: center;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/ui/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | }
8 |
--------------------------------------------------------------------------------