Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
591eb91
refactor(phase-0): stabilize MVP before multi-module refactor
euskadi31 May 19, 2026
5635784
refactor(phase-1): introduce go.work multi-module layout
euskadi31 May 19, 2026
f42acb1
refactor(phase-2): introduce transport-agnostic security core
euskadi31 May 19, 2026
ba10173
refactor(phase-3): introduce httpsec — net/http adapter for the secur…
euskadi31 May 19, 2026
e9c69e9
refactor(phase-4): extract password module + introduce basic and bearer
euskadi31 May 19, 2026
d37d739
refactor(phase-5): authorization v2 — concrete attributes + voter cat…
euskadi31 May 19, 2026
c43f4a8
refactor(phase-6): introduce jwtsec — signer, verifier, JWKS, bearer …
euskadi31 May 19, 2026
1cf8f48
refactor(phase-7a): OAuth2 foundations — models, errors, storage, tok…
euskadi31 May 19, 2026
fa824a6
refactor(phase-7b): JWT access-token bridge (oauth2 <-> jwtsec)
euskadi31 May 19, 2026
61e7c53
refactor(phase-7c): OAuth2 grants and client authentication methods
euskadi31 May 19, 2026
e89f9a7
refactor(phase-7d): OAuth2 Server, Profile, IssuerResolver, endpoints
euskadi31 May 19, 2026
f0d7b43
refactor(phase-7e): remove the legacy v0 stack
euskadi31 May 20, 2026
3a739b6
refactor(phase-8a): shared oauth2.Storage conformance suite
euskadi31 May 20, 2026
29d1f58
refactor(phase-8b): production SQL store (database/sql, 3 dialects)
euskadi31 May 20, 2026
a2aa4ed
refactor(phase-8c): production Redis store (go-redis + Lua atomicity)
euskadi31 May 20, 2026
9102b17
refactor(phase-9): introduce grpcsec — gRPC transport adapter
euskadi31 May 20, 2026
b2f6d86
refactor(phase-10): introduce session — encrypted cookie sessions + CSRF
euskadi31 May 20, 2026
7880b92
docs(phase-11a): architecture, observability, security, migration guides
euskadi31 May 20, 2026
83f8b99
docs(phase-11b): runnable examples and multi-module release workflow
euskadi31 May 20, 2026
4ed800c
test(oauth2): cover the oauth2 package and clientauth methods
euskadi31 May 20, 2026
1299f73
test(core): cover the attribute constructors
euskadi31 May 20, 2026
326dfd7
test(oauth2): raise grant and token coverage above 80%
euskadi31 May 20, 2026
ef1be36
test(jwt): raise coverage above 80%
euskadi31 May 20, 2026
3fd3d5e
test(oauth2/store): cover SQL and Redis store error paths
euskadi31 May 20, 2026
a626f5d
test(oauth2): cover the conformance harness and the oauth2 example
euskadi31 May 20, 2026
9b8416b
chore: sync go.work.sum after coverage test additions
euskadi31 May 20, 2026
608db8c
test: cover accessor and option functions left at 0%
euskadi31 May 20, 2026
22b9b2e
refactor: merge example/ into examples/, drop examples from coverage
euskadi31 May 20, 2026
2d27fb8
chore: remove pre-refactor legacy and dead code
euskadi31 May 20, 2026
fcc141b
refactor(bearer): remove the deprecated query-parameter extractor
euskadi31 May 20, 2026
965e4b7
feat(oauth2): configurable RoutePrefix for the metadata document
euskadi31 May 20, 2026
9380771
feat(oauth2): apply the security Profile to the grant runtime
euskadi31 May 20, 2026
3bb11b7
feat(oauth2): /authorize endpoint (authorization_code flow)
euskadi31 May 20, 2026
51aab14
feat(oauth2): legacy password grant (RFC 6749 §4.3), opt-in
euskadi31 May 20, 2026
bd89a02
feat(oauth2): implicit flow on /authorize (response_type=token), opt-in
euskadi31 May 20, 2026
0db648b
docs(oauth2): document the /authorize endpoint and legacy grants
euskadi31 May 21, 2026
0acc9ff
fix(oauth2): consistent token hashing — drop the generator pepper
euskadi31 May 21, 2026
0e3ceb8
feat: add claude.md
euskadi31 May 21, 2026
04dc1f8
fix: use last go version
euskadi31 May 21, 2026
1721ef5
fix(http): stop reflecting internal error text into WWW-Authenticate
euskadi31 May 21, 2026
472ae6d
feat(jwt): require the exp claim by default (fail-closed)
euskadi31 May 21, 2026
c2c1c8e
fix: ci
euskadi31 May 21, 2026
c12954a
fix(examples): silence goconst in basic-http user store
euskadi31 May 21, 2026
e79723e
fix(http): extract Bearer challenge scheme into a constant
euskadi31 May 21, 2026
92f070b
fix(jwt): extract JWK signature use into a constant
euskadi31 May 21, 2026
ac207d2
fix(oauth2): clear goconst and gosec lint findings
euskadi31 May 21, 2026
85c5d69
fix(session): annotate G124 on configurable cookie attributes
euskadi31 May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"permissions": {
"allow": [
"Bash(git commit -m ' *)",
"Bash(go work *)",
"Bash(make build *)",
"Bash(make test *)",
"Bash(make lint *)",
"Bash(git add *)",
"Bash(git rm *)",
"Bash(git mv *)",
"Bash(sed *)",
"Bash(awk *)",
"Read(//Users/euskadi31/Projects/Github/hyperscale-stack/**)"
]
}
}
42 changes: 25 additions & 17 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,43 @@ on:

jobs:
build:
name: Build
name: Build & test (workspace)
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go 1.x
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.x"
check-latest: true
go-version-file: go.work
id: go

- name: Build
run: make build
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
install-only: true

- name: Generate
run: make generate
- name: Workspace sync
run: make sync

- name: Test
# NOTE: `make generate` is intentionally NOT run in CI yet — the
# .mockery.yaml is being migrated to v3 syntax while the tool pin
# (vektra/mockery v2.53.5) is still v2. Re-enable once the config /
# tool are aligned (tracked in LIMITATIONS.md, slated for Phase 4).
- name: Build all modules
run: make build

- name: Test all modules (race + coverage)
run: make test

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: latest
skip-cache: true
# Lint runs in a dedicated step so that gosec/golangci output is easy to
# read. The Makefile iterates over every module with the shared config.
- name: Lint all modules
run: make lint

- name: Coveralls
- name: Upload aggregated coverage to Coveralls
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: build/coverage.out
Expand All @@ -45,7 +53,7 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
- name: Coveralls finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Release

# Each module of the workspace is released independently. Go's multi-module
# convention puts the module path in the tag:
#
# v1.2.3 -> the root module (github.com/hyperscale-stack/security)
# http/v1.2.3 -> the github.com/hyperscale-stack/security/http module
# oauth2/v1.2.3 -> the github.com/hyperscale-stack/security/oauth2 module
#
# Pushing such a tag validates the tagged state and publishes a GitHub
# release. Nothing is force-pushed and no tag is created by this workflow.
on:
push:
tags:
- "v*"
- "*/v*"

permissions:
contents: write

jobs:
release:
name: Release ${{ github.ref_name }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.x"
check-latest: true

- name: Resolve the released module
id: mod
run: |
tag='${{ github.ref_name }}'
case "$tag" in
*/v*) echo "dir=${tag%/v*}" >> "$GITHUB_OUTPUT" ;;
*) echo "dir=." >> "$GITHUB_OUTPUT" ;;
esac

- name: Workspace sync
run: make sync

# The whole workspace is built and tested so a tag can never publish a
# module whose dependencies (sibling modules) are in a broken state.
- name: Build all modules
run: make build

- name: Test all modules (race + coverage)
run: make test

- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
generate_release_notes: true
body: |
Release of the `${{ steps.mod.outputs.dir }}` module.

See [CHANGELOG.md](https://github.com/hyperscale-stack/security/blob/master/CHANGELOG.md)
for the consolidated history.
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ formatters:
- gofmt
exclusions:
paths:
- .github/.*
- .claude/.*
- .vscode/.*
- build/.*
- docs/.*
- .*_mock\.go
- mock_.*\.go
- .*/pkg/mod/.*$
Expand Down
20 changes: 9 additions & 11 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
inpackage: True
with-expecter: True
all: True
all: true
dir: '{{.InterfaceDir}}'
mockname: 'Mock{{.InterfaceName}}'
outpkg: '{{.PackageName}}'
filename: 'mock_{{ .InterfaceName | snakecase }}.go'
filename: 'mock_{{.InterfaceName | snakecase}}.go'
structname: Mock{{.InterfaceName}}
pkgname: '{{.SrcPackageName}}'
inpackage: true
template: testify
template-data:
unroll-variadic: true
packages:
github.com/hyperscale-stack/security:
config:
recursive: True

issue-845-fix: True
resolve-type-alias: False
disable-version-string: True
recursive: true
101 changes: 101 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Changelog

All notable changes to this project are documented in this file. The format
is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

The library is a multi-module workspace; modules are tagged independently
(`module/vX.Y.Z`). The entries below describe the ground-up rewrite that
replaced the v0 stack.

## [Unreleased]

The whole `v0.x` series is superseded by a transport-agnostic rewrite. The
legacy packages (`authentication/`, `authorization/`, the in-tree
`password` package, `authentication/provider/oauth2`) were removed.

### Added

- **Transport-agnostic core** (`github.com/hyperscale-stack/security`):
immutable `Authentication`/`Principal`, `Carrier`, `Extractor`,
`Authenticator`, first-success-wins `Manager`, `Engine`, typed
`SecurityError` sentinels, and a `Clock` abstraction.
- **Authorization v2**: `Voter`/`Decision`/`Attribute`, an
`AccessDecisionManager` with Affirmative/Consensus/Unanimous strategies,
and a `voter/` catalog (`HasRole`, `HasAnyRole`, `HasScope`,
`HasAuthority`, `HasPermission`, `Authenticated`, `Anonymous`,
`FullyAuthenticated`, `And`/`Or`/`Not`).
- **HTTP adapter** (`httpsec`): `Middleware`, `Authorize`, a request/response
`Carrier`, and a configurable `ErrorMapper`.
- **gRPC adapter** (`grpcsec`): unary and stream server interceptors,
`UnaryAuthorize`/`StreamAuthorize`, a `metadata.MD` carrier, and an
`ErrorMapper` to `codes.Code`.
- **Schemes**: `basic` (HTTP Basic extractor + authenticator) and `bearer`
(Bearer extractor + pluggable `TokenVerifier`).
- **Password hashing** (`password`): `Hasher` interface with bcrypt and
Argon2id implementations, context support, and `NeedsRehash`.
- **JWT** (`jwtsec`): `Signer`/`Verifier`, static and cached-remote JWKS,
key rotation, `alg=none` and algorithm-confusion defenses, and a
`bearer.TokenVerifier` adapter.
- **OAuth2 server** (`oauth2`): `Profile` (2.0 / 2.0-BCP / 2.1-draft),
enforced at runtime on the grants (PKCE required, `plain` PKCE refused
under BCP / 2.1). Grants: `authorization_code` (PKCE), `client_credentials`,
`refresh_token` (rotation + reuse detection), and the opt-in legacy
`password` grant (`grant.NewLegacyPassword`, refused outside `Profile20`).
`client_secret_basic`/`_post`/`none` client authentication. Endpoints:
`/authorize` (authorization_code + opt-in legacy implicit flow, with an
application-supplied consent hook), `/token`, `/revoke`, `/introspect`,
and metadata — the metadata endpoint paths are configurable through
`ServerConfig.RoutePrefix`. A `Storage` interface with explicit atomicity
contracts.
- **OAuth2 storage backends**: in-memory (`oauth2/storage/memory`), SQL
(`oauth2/store/sql`, Postgres/MySQL/SQLite), and Redis
(`oauth2/store/redis`, Lua-script atomicity), all validated by the shared
`oauth2/storetest` conformance suite.
- **Sessions** (`session`): stateless AES-256-GCM encrypted cookies with key
rotation, a `Manager` (Login/Get/Touch/Rotate/Logout), and a
synchronizer-token CSRF helper.
- **Observability**: OpenTelemetry spans emitted directly by the core,
`httpsec`, `grpcsec`, `jwtsec`, and `session`. See
[docs/observability.md](docs/observability.md).
- **Documentation**: `docs/architecture.md`, `docs/observability.md`,
`docs/security-considerations.md`, `docs/migration-from-v0.md`, and a
refreshed `README.md`.

### Changed

- The repository is now a Go workspace (`go.work`) of independent modules,
so consumers import only the pieces they need and the core stays free of
heavy transitive dependencies.
- `Authentication` is immutable — authenticators return new values instead
of mutating their input.
- `context.Context` is the first argument of every runtime operation
(`Extract`, `Authenticate`, `Hasher.Hash`/`Verify`, `TokenVerifier.Verify`).
- Password `Verify` returns `(bool, error)`, distinguishing a mismatch from
a malformed hash; v0 returned a bare `bool`.
- The JWT verifier (`jwtsec`) now rejects tokens without an `exp` claim by
default (`ErrMissingExpiry`), aligning with RFC 9068 §2.2 and the
fail-closed doctrine. Opt out with `jwtsec.WithOptionalExpiry()` to verify
deliberately non-expiring assertions.

### Fixed

- The v0 authentication `Handler` no longer iterates past a successful
authentication and no longer swallows provider errors — the `Manager`
short-circuits on first success and aggregates failures.
- The OAuth2 client-secret mismatch is now a typed error
(`ErrClientSecretMismatch`) instead of a silent failure.

### Removed

- The legacy v0 packages: `authentication/`, `authentication/credential/`,
`authentication/provider/{dao,oauth2}/`, `authorization/`, and the
in-tree `password` package.

### Security

- The HTTP `DefaultErrorMapper` no longer reflects the wrapped error chain
into the `WWW-Authenticate` header. The RFC 6750 `error_description` is now
a fixed, generic string per error code, so internal context (timestamps,
package and authenticator names, consumer-supplied `TokenVerifier`/store
errors) can no longer leak to clients.
Loading
Loading