Skip to content

Commit 9bd217f

Browse files
Merge pull request linuxfoundation#17 from mauriciozanettisalomao/feat/lfxv2-475-jwt-auth-refactoring
[LFXV2-475] Refactor JWT authentication for consistency with other services
2 parents 9f13a67 + fd0966b commit 9bd217f

File tree

9 files changed

+256
-81
lines changed

9 files changed

+256
-81
lines changed

README.md

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The implementation follows the clean architecture principles where:
2020
├── design/ # GOA design specification files
2121
├── gen/ # GOA generated code (HTTP server, client, OpenAPI)
2222
├── cmd/ # Services (main packages)
23-
│ └── query_svc/ # Query service implementation
23+
│ └── service/ # Service implementation
2424
├── internal/ # Internal service packages
2525
│ ├── domain/ # Domain logic layer
2626
│ │ ├── model/ # Domain models and entities
@@ -45,7 +45,9 @@ The implementation follows the clean architecture principles where:
4545
#### Ports (`internal/domain/port/`)
4646

4747
- **ResourceSearcher Interface**: Defines the contract for resource search operations
48+
- **OrganizationSearcher Interface**: Defines the contract for organization search operations
4849
- **AccessControlChecker Interface**: Defines the contract for access control operations
50+
- **Authenticator Interface**: Defines the contract for authentication operations
4951

5052
### Service Layer (`internal/service/`)
5153

@@ -54,6 +56,26 @@ The implementation follows the clean architecture principles where:
5456

5557
### Infrastructure Layer (`internal/infrastructure/`)
5658

59+
#### Authentication Implementation
60+
61+
The authentication system provides JWT-based authentication with support for Heimdall tokens:
62+
63+
- **JWT Authentication (`internal/infrastructure/auth/jwt.go`)**:
64+
- Validates JWT tokens using JWKS (JSON Web Key Set)
65+
- Extracts custom claims including principal and email
66+
- Supports PS256 signature algorithm (default for Heimdall)
67+
- Configurable JWKS URL and audience
68+
69+
- **Mock Authentication (`internal/infrastructure/mock/auth.go`)**:
70+
- Uses environment variable for mock principal
71+
- Bypasses JWT validation for local development
72+
73+
**Authentication Configuration:**
74+
- `AUTH_SOURCE`: Choose between "mock" or "jwt" (default: "jwt")
75+
- `JWKS_URL`: JSON Web Key Set endpoint URL
76+
- `JWT_AUDIENCE`: Intended audience for JWT tokens
77+
- `JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL`: Mock principal for development
78+
5779
#### OpenSearch Implementation
5880

5981
The OpenSearch implementation includes query templates, a searcher, and a client for interacting with the OpenSearch cluster.
@@ -162,6 +184,13 @@ go run cmd/main.go
162184
- `CLEARBIT_MAX_RETRIES`: Maximum number of retry attempts for failed requests (default: "3")
163185
- `CLEARBIT_RETRY_DELAY`: Delay between retry attempts (default: "1s")
164186

187+
**Authentication Configuration:**
188+
189+
- `AUTH_SOURCE`: Choose between "mock" or "jwt"
190+
- `JWKS_URL`: JSON Web Key Set endpoint URL
191+
- `JWT_AUDIENCE`: Intended audience for JWT tokens
192+
- `JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL`: Mock principal for development (required when AUTH_SOURCE=mock)
193+
165194
**Server Configuration:**
166195

167196
- `-p`: HTTP port (default: "8080")
@@ -170,10 +199,13 @@ go run cmd/main.go
170199

171200
### API Usage
172201

173-
The service exposes a RESTful API through the Goa framework:
202+
The service exposes a RESTful API through the Goa framework with JWT authentication:
203+
204+
#### Resource Search API
174205

175206
```
176207
GET /query/resources?name=committee&type=committee&v=1
208+
Authorization: Bearer <jwt_token>
177209
```
178210

179211
**Parameters:**
@@ -206,6 +238,59 @@ GET /query/resources?name=committee&type=committee&v=1
206238
}
207239
```
208240

241+
#### Organization Search API
242+
243+
**Query Organizations:**
244+
245+
```
246+
GET /query/orgs?name=Linux Foundation&domain=linuxfoundation.org&v=1
247+
Authorization: Bearer <jwt_token>
248+
```
249+
250+
**Parameters:**
251+
252+
- `name`: Organization name (optional)
253+
- `domain`: Organization domain or website URL (optional)
254+
- `v`: API version (required)
255+
256+
**Response:**
257+
258+
```json
259+
{
260+
"name": "Linux Foundation",
261+
"domain": "linuxfoundation.org",
262+
"industry": "Non-Profit",
263+
"sector": "Technology",
264+
"employees": "100-499"
265+
}
266+
```
267+
268+
**Organization Suggestions API:**
269+
270+
```
271+
GET /query/orgs/suggest?query=linux&v=1
272+
Authorization: Bearer <jwt_token>
273+
```
274+
275+
**Parameters:**
276+
277+
- `query`: Search query for organization suggestions (required, minimum 1 character)
278+
- `v`: API version (required)
279+
280+
**Response:**
281+
282+
```json
283+
{
284+
"suggestions": [
285+
{
286+
"name": "Linux Foundation",
287+
"domain": "linuxfoundation.org",
288+
"logo": "https://example.com/logo.png"
289+
}
290+
]
291+
}
292+
```
293+
209294
## Clearbit API Integration
210295

211296
The service integrates with Clearbit's Company API to provide enriched organization data for search operations. This integration allows the service to fetch detailed company information including industry classification, employee count, and domain information.

charts/lfx-v2-query-service/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ apiVersion: v2
55
name: lfx-v2-query-service
66
description: LFX Platform V2 Query Service chart
77
type: application
8-
version: 0.4.1
8+
version: 0.4.2
99
appVersion: "latest"

charts/lfx-v2-query-service/values.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ app:
2828
secretKeyRef:
2929
name: lfx-v2-query-service-secrets
3030
key: PAGE_TOKEN_SECRET
31-
# CLEARBIT_CREDENTIAL is optional
31+
## In case of clearbit, the value is required to be set.
32+
## Optionally, it's possible to use a mock implementation.
33+
## In case of mock, please set the ORG_SEARCH_SOURCE to mock.
3234
CLEARBIT_CREDENTIAL:
3335
value: null
3436

cmd/main.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,14 @@ func main() {
5858
resourceSearcher := service.SearcherImpl(ctx)
5959
accessControlChecker := service.AccessControlCheckerImpl(ctx)
6060
organizationSearcher := service.OrganizationSearcherImpl(ctx)
61+
authService := service.AuthServiceImpl(ctx)
6162

6263
// Initialize the services.
6364
var (
6465
querySvcSvc querysvc.Service
6566
)
6667
{
67-
querySvcSvc = service.NewQuerySvc(resourceSearcher, accessControlChecker, organizationSearcher)
68+
querySvcSvc = service.NewQuerySvc(resourceSearcher, accessControlChecker, organizationSearcher, authService)
6869
}
6970

7071
// Wrap the services in endpoints that can be invoked from other services
@@ -87,9 +88,6 @@ func main() {
8788
var wg sync.WaitGroup
8889
ctx, cancel := context.WithCancel(ctx)
8990

90-
// Setup the JWT authentication which validates and parses the JWT token.
91-
service.SetupJWTAuth(ctx)
92-
9391
// Start the servers and send errors (if any) to the error channel.
9492
addr := ":" + *port
9593
if *bind != "*" {

cmd/service/providers.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,45 @@ import (
1212
"time"
1313

1414
"github.com/linuxfoundation/lfx-v2-query-service/internal/domain/port"
15+
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/auth"
1516
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/clearbit"
1617
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/mock"
1718
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/nats"
1819
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/opensearch"
1920
)
2021

22+
// AuthServiceImpl initializes the authentication service implementation
23+
func AuthServiceImpl(ctx context.Context) port.Authenticator {
24+
var authService port.Authenticator
25+
26+
// Repository implementation configuration
27+
authSource := os.Getenv("AUTH_SOURCE")
28+
if authSource == "" {
29+
authSource = "jwt"
30+
}
31+
32+
switch authSource {
33+
case "mock":
34+
slog.InfoContext(ctx, "initializing mock authentication service")
35+
authService = mock.NewMockAuthService()
36+
case "jwt":
37+
slog.InfoContext(ctx, "initializing JWT authentication service")
38+
jwtConfig := auth.JWTAuthConfig{
39+
JWKSURL: os.Getenv("JWKS_URL"),
40+
Audience: os.Getenv("JWT_AUDIENCE"),
41+
}
42+
jwtAuth, err := auth.NewJWTAuth(jwtConfig)
43+
if err != nil {
44+
log.Fatalf("failed to initialize JWT authentication service: %v", err)
45+
}
46+
authService = jwtAuth
47+
default:
48+
log.Fatalf("unsupported authentication service implementation: %s", authSource)
49+
}
50+
51+
return authService
52+
}
53+
2154
// SearcherImpl injects the resource searcher implementation
2255
func SearcherImpl(ctx context.Context) port.ResourceSearcher {
2356

cmd/service/service.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ import (
2020
type querySvcsrvc struct {
2121
resourceService service.ResourceSearcher
2222
organizationService service.OrganizationSearcher
23+
auth port.Authenticator
2324
}
2425

2526
// JWTAuth implements the authorization logic for service "query-svc" for the
2627
// "jwt" security scheme.
2728
func (s *querySvcsrvc) JWTAuth(ctx context.Context, token string, scheme *security.JWTScheme) (context.Context, error) {
2829

2930
// Parse the Heimdall-authorized principal from the token.
30-
principal, err := ParsePrincipal(ctx, token)
31+
principal, err := s.auth.ParsePrincipal(ctx, token, slog.Default())
3132
if err != nil {
32-
return ctx, err
33+
return ctx, wrapError(ctx, err)
3334
}
3435

3536
// Log the principal for debugging purposes in all logs for this request.
@@ -132,11 +133,13 @@ func (s *querySvcsrvc) Livez(ctx context.Context) (res []byte, err error) {
132133
func NewQuerySvc(resourceSearcher port.ResourceSearcher,
133134
accessControlChecker port.AccessControlChecker,
134135
organizationSearcher port.OrganizationSearcher,
136+
auth port.Authenticator,
135137
) querysvc.Service {
136138
resourceService := service.NewResourceSearch(resourceSearcher, accessControlChecker)
137139
organizationService := service.NewOrganizationSearch(organizationSearcher)
138140
return &querySvcsrvc{
139141
resourceService: resourceService,
140142
organizationService: organizationService,
143+
auth: auth,
141144
}
142145
}

internal/domain/port/auth.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright The Linux Foundation and each contributor to LFX.
2+
// SPDX-License-Identifier: MIT
3+
4+
package port
5+
6+
import (
7+
"context"
8+
"log/slog"
9+
)
10+
11+
// Authenticator defines the interface for authentication operations
12+
type Authenticator interface {
13+
// ParsePrincipal parses and validates a JWT token, returning the principal
14+
ParsePrincipal(ctx context.Context, token string, logger *slog.Logger) (string, error)
15+
}

0 commit comments

Comments
 (0)