Skip to content

Commit c6a7211

Browse files
committed
chore: resolve issues with print service/client/auth
1 parent 8d42e29 commit c6a7211

18 files changed

Lines changed: 414 additions & 94 deletions

File tree

docker-compose.yml

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ services:
249249
- "5435:5432"
250250
volumes:
251251
- print-service-data:/var/lib/postgresql/data
252+
- ./shared/infra/compose/postgres-init:/docker-entrypoint-initdb.d:ro
253+
command:
254+
- postgres
255+
- -c
256+
- shared_preload_libraries=pg_stat_statements
257+
- -c
258+
- pg_stat_statements.track=all
252259
healthcheck:
253260
test: ["CMD-SHELL", "pg_isready -U print_service_user -d print_service"]
254261
interval: 1s
@@ -348,7 +355,7 @@ services:
348355
dockerfile: src/Stickerlandia.PrintService.Api/Dockerfile
349356
container_name: print-service
350357
depends_on:
351-
redpanda:
358+
kafka:
352359
condition: service_healthy
353360
print-service-db-migrations:
354361
condition: service_completed_successfully
@@ -370,10 +377,10 @@ services:
370377
DRIVING: AGNOSTIC
371378
DRIVEN: AGNOSTIC
372379
DISABLE_SSL: "true"
373-
ConnectionStrings__messaging: "redpanda:9092"
380+
ConnectionStrings__messaging: "kafka:29092"
374381
ConnectionStrings__database: "Host=print-service-db;Port=5432;Database=print_service;Username=print_service_user;Password=print_service_password"
375-
KAFKA__BOOTSTRAPSERVERS: "redpanda:9092"
376-
KAFKA__SCHEMAREGISTRY: "http://redpanda:8082"
382+
KAFKA__BOOTSTRAPSERVERS: "kafka:9092"
383+
KAFKA__SCHEMAREGISTRY: "http://kafka:8082"
377384
KAFKA__GROUPID: "stickerlandia-print"
378385
Authentication__Audience: "stickerlandia"
379386
Authentication__Authority: ${DEPLOYMENT_HOST_URL}
@@ -384,7 +391,7 @@ services:
384391
DEPLOYMENT_HOST_URL: ${DEPLOYMENT_HOST_URL}
385392
healthcheck:
386393
test: ["CMD", "curl", "-f", "http://localhost:8080/api/print/v1/health"]
387-
interval: 1s
394+
interval: 120s
388395
timeout: 15s
389396
retries: 10
390397
labels:
@@ -401,6 +408,8 @@ services:
401408
depends_on:
402409
print-service-db:
403410
condition: service_healthy
411+
kafka:
412+
condition: service_healthy
404413
environment:
405414
# DataDog APM configuration
406415
DD_SERVICE: print-service-migration-service
@@ -413,10 +422,10 @@ services:
413422
ASPNETCORE_ENVIRONMENT: Development
414423
DRIVING: AGNOSTIC
415424
DRIVEN: AGNOSTIC
416-
ConnectionStrings__messaging: "redpanda:9092"
425+
ConnectionStrings__messaging: "kafka:29092"
417426
ConnectionStrings__database: "Host=print-service-db;Port=5432;Database=print_service;Username=print_service_user;Password=print_service_password"
418-
KAFKA__BOOTSTRAPSERVERS: "redpanda:9092"
419-
KAFKA__SCHEMAREGISTRY: "http://redpanda:8082"
427+
KAFKA__BOOTSTRAPSERVERS: "kafka:9092"
428+
KAFKA__SCHEMAREGISTRY: "http://kafka:8082"
420429
KAFKA__GROUPID: "stickerlandia-print"
421430
DEPLOYMENT_HOST_URL: ${DEPLOYMENT_HOST_URL}
422431
command: ["migrations/Stickerlandia.PrintService.MigrationService.dll"]
@@ -519,6 +528,8 @@ services:
519528
depends_on:
520529
user-management-db:
521530
condition: service_healthy
531+
kafka:
532+
condition: service_healthy
522533
environment:
523534
# DataDog APM configuration
524535
DD_SERVICE: user-management-migration-service
@@ -778,3 +789,9 @@ configs:
778789
username: datadog
779790
password: datadog_password
780791
dbname: sticker_catalogue
792+
- dbm: true
793+
host: print-service-db
794+
port: 5432
795+
username: datadog
796+
password: datadog_password
797+
dbname: print_service

print-service/src/Stickerlandia.PrintService.Api/Configurations/AuthenticationExtensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public static IServiceCollection AddPrintServiceAuthentication(
2525
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
2626
.AddJwtBearer(options =>
2727
{
28+
// Prevent mapping "role" → long ClaimTypes.Role URI so that
29+
// RoleClaimType = "role" matches the actual JWT claim name.
30+
options.MapInboundClaims = false;
31+
2832
if (authMode.Equals("OidcDiscovery", StringComparison.OrdinalIgnoreCase))
2933
{
3034
ConfigureOidcDiscovery(options, configuration);
@@ -70,7 +74,8 @@ private static void ConfigureOidcDiscovery(JwtBearerOptions options, IConfigurat
7074
ValidAudience = audience,
7175
ValidateLifetime = true,
7276
ValidateIssuerSigningKey = true,
73-
ClockSkew = TimeSpan.FromMinutes(5)
77+
ClockSkew = TimeSpan.FromMinutes(5),
78+
RoleClaimType = "role"
7479
};
7580

7681
// If a separate metadata address is configured, use it for all OIDC fetching
@@ -82,13 +87,15 @@ private static void ConfigureOidcDiscovery(JwtBearerOptions options, IConfigurat
8287
metadataAddress += "/";
8388
}
8489

85-
using var httpHandler = new HttpClientHandler();
90+
var httpHandler = new HttpClientHandler();
8691
if (!requireHttpsMetadata)
8792
{
8893
httpHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
8994
}
9095

91-
using var httpClient = new HttpClient(httpHandler);
96+
// Do NOT use 'using var' here — the HttpClient is captured by the
97+
// IssuerSigningKeyResolver lambda and must survive beyond this method.
98+
var httpClient = new HttpClient(httpHandler);
9299
var internalJwksUrl = metadataAddress + ".well-known/jwks";
93100

94101
// Use IssuerSigningKeyResolver to dynamically fetch keys from internal URL
@@ -135,7 +142,8 @@ private static void ConfigureSymmetricKey(JwtBearerOptions options, IConfigurati
135142
ValidateLifetime = true,
136143
ValidateIssuerSigningKey = true,
137144
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(signingKey)),
138-
ClockSkew = TimeSpan.FromMinutes(5)
145+
ClockSkew = TimeSpan.FromMinutes(5),
146+
RoleClaimType = "role"
139147
};
140148
}
141149
}

print-service/src/Stickerlandia.PrintService.Client/print-jobs/2026-01-15/28969626-0aaf-4b69-b059-3cc2186ee4cd.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

print-service/src/Stickerlandia.PrintService.Client/print-jobs/2026-01-15/e799f018-7edf-4e9c-bd47-701695dd627f.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

print-service/src/Stickerlandia.PrintService.Core/PrintJobs/SubmitPrintJobCommand.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ public bool IsValid()
2424
{
2525
return !string.IsNullOrEmpty(UserId)
2626
&& !string.IsNullOrEmpty(StickerId)
27-
&& !string.IsNullOrEmpty(StickerUrl)
28-
&& Uri.TryCreate(StickerUrl, UriKind.Absolute, out var uri)
29-
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
27+
&& !string.IsNullOrEmpty(StickerUrl);
3028
}
3129
}

print-service/src/Stickerlandia.PrintService.Core/RegisterPrinter/RegisterPrinterResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ public RegisterPrinterResponse(Printer printer) : base(printer)
1919
Key = printer.Key;
2020
}
2121

22-
[JsonPropertyName("Key")] public string Key { get; set; } = string.Empty;
22+
[JsonPropertyName("key")] public string Key { get; set; } = string.Empty;
2323
}

user-management/src/Stickerlandia.UserManagement.Auth/AuthServiceExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ public static IServiceCollection AddCoreAuthentication(this IServiceCollection s
122122
context.EmailVerified = false;
123123
}
124124

125+
if (context.AccessTokenPrincipal.HasScope(OpenIddictConstants.Scopes.Roles))
126+
{
127+
var roles = context.AccessTokenPrincipal.GetClaims(OpenIddictConstants.Claims.Role);
128+
if (roles.Any())
129+
{
130+
context.Claims[OpenIddictConstants.Claims.Role] =
131+
System.Text.Json.JsonSerializer.SerializeToElement(roles.ToList());
132+
}
133+
}
134+
125135
return default;
126136
}));
127137
})

user-management/src/Stickerlandia.UserManagement.MigrationService/Worker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ await manager.CreateAsync(new OpenIddictApplicationDescriptor
167167
{
168168
EmailAddress = "admin@stickerlandia.com",
169169
FirstName = "Default",
170-
LastName = "User",
170+
LastName = "Admin",
171171
Password = "Admin2025!"
172172
}, AccountType.Admin);
173173

web-backend/server.js

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ app.use(lusca({
6363
secret: process.env.CSRF_SECRET || 'dev-csrf-secret-change-in-production',
6464
cookie: true, // Store CSRF token in cookie
6565
header: 'x-csrf-token', // Accept token in header
66-
whitelist: ['/api/app/auth/callback'] // Skip CSRF for OAuth callback
66+
whitelist: ['/api/app/auth/callback', '/api/app/auth/token'] // Skip CSRF for OAuth callback and token exchange
6767
},
6868
csp: false, // Disable Lusca's CSP (we use Helmet)
6969
xframe: false, // Disable Lusca's X-Frame-Options (we use Helmet)
@@ -182,9 +182,6 @@ app.post('/api/app/auth/login', loginRateLimit, (req, res) => {
182182
// Step 3: IdP redirects back to BFF with authorization code
183183
app.get('/api/app/auth/callback', async (req, res) => {
184184
try {
185-
console.debug('Callback URL:', req.url)
186-
console.debug('Query params:', req.query)
187-
188185
if (!client) {
189186
return res.status(500).send('OIDC client not initialized')
190187
}
@@ -212,29 +209,11 @@ app.get('/api/app/auth/callback', async (req, res) => {
212209
}
213210

214211
// Exchange authorization code for tokens
215-
console.debug('Attempting token exchange with:', {
216-
redirect_uri: sessionOAuth.redirect_uri,
217-
code: code.substring(0, 10) + '...',
218-
state: state.substring(0, 10) + '...',
219-
issuer: client.issuer.issuer
220-
})
221-
222-
console.debug('Full callback parameters:', { code, state })
223-
console.debug('Session OAuth data:', sessionOAuth)
224-
console.debug('Client issuer metadata:', {
225-
issuer: client.issuer.issuer,
226-
token_endpoint: client.issuer.token_endpoint
227-
})
228-
229-
// Debug what we're passing to callback
230-
const checks = {
212+
const checks = {
231213
code_verifier: sessionOAuth.code_verifier,
232214
nonce: sessionOAuth.nonce,
233215
state: sessionOAuth.state
234216
}
235-
console.debug('Callback params:', callbackParams)
236-
console.debug('Callback checks:', checks)
237-
console.debug('Using configured redirect URI:', sessionOAuth.redirect_uri)
238217

239218
const tokenSet = await client.callback(sessionOAuth.redirect_uri, callbackParams, checks)
240219

@@ -255,11 +234,13 @@ app.get('/api/app/auth/callback', async (req, res) => {
255234
// Clear OAuth temp data
256235
delete req.session.oauth
257236

258-
// Redirect back to frontend with token in query params (for new client-side flow)
259-
const queryParams = new URLSearchParams()
260-
queryParams.set('access_token', tokenSet.access_token)
261-
queryParams.set('expires_at', tokenSet.expires_at)
262-
res.redirect(`/?${queryParams.toString()}`)
237+
// Flag that a token is ready for pickup via /api/app/auth/token
238+
req.session.pendingToken = {
239+
access_token: tokenSet.access_token,
240+
expires_at: tokenSet.expires_at
241+
}
242+
243+
res.redirect('/?auth=complete')
263244
} catch (error) {
264245
console.error('OAuth callback failed:', error)
265246
res.status(400).send('Authentication failed')
@@ -300,6 +281,22 @@ app.get('/api/app/auth/user', async (req, res) => {
300281
}
301282
})
302283

284+
// One-time token exchange — frontend calls this after ?auth=complete redirect
285+
app.post('/api/app/auth/token', (req, res) => {
286+
const pending = req.session.pendingToken
287+
if (!pending) {
288+
return res.status(404).json({ error: 'No pending token' })
289+
}
290+
291+
// Clear immediately so the token can only be retrieved once
292+
delete req.session.pendingToken
293+
294+
res.json({
295+
access_token: pending.access_token,
296+
expires_at: pending.expires_at
297+
})
298+
})
299+
303300
// Logout - clear session and optionally call IdP logout
304301
app.post('/api/app/auth/logout', (req, res) => {
305302
// Capture id_token before destroying session
@@ -308,7 +305,7 @@ app.post('/api/app/auth/logout', (req, res) => {
308305
// Clear session and cookie
309306
req.session.destroy((err) => {
310307
if (err) console.error('Session destroy error:', err)
311-
res.clearCookie('connect.sid', { path: '/' })
308+
res.clearCookie('sessionId', { path: '/' })
312309

313310
if (idToken && client) {
314311
// Generate IdP logout URL with correct deployment host

web-frontend/src/components/StickerDetail.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { API_BASE_URL } from "../config";
77
import AuthService from "../services/AuthService";
88
import { useAuth } from "../context/AuthContext";
99
import { authFetch } from "../utils/authFetch";
10+
import { getLastActiveEvent } from "../services/eventStorage";
1011

1112
function StickerDetail() {
1213
const { id } = useParams();
@@ -164,7 +165,13 @@ function StickerDetail() {
164165
</div>
165166
<div className="flex justify-center">
166167
<button
167-
onClick={() => navigate('/print-station', { state: { sticker } })}
168+
onClick={() => {
169+
const lastEvent = getLastActiveEvent()
170+
const path = lastEvent
171+
? `/print-station/${encodeURIComponent(lastEvent)}`
172+
: '/print-station'
173+
navigate(path, { state: { sticker } })
174+
}}
168175
className="flex items-center gap-2 px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
169176
>
170177
<LocalPrintshopOutlinedIcon />

0 commit comments

Comments
 (0)