Skip to content

Commit 9b0cfc0

Browse files
committed
fix(cloud_auth): Ensure session is linked to user cork
1 parent 43dd398 commit 9b0cfc0

File tree

8 files changed

+132
-30
lines changed

8 files changed

+132
-30
lines changed

services/celest_cloud_auth/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 0.1.0+2
22

33
- fix: Ensure Cedar types are registered during seeding
4+
- fix: Ensure session is linked to user cork
45

56
## 0.1.0+1
67

services/celest_cloud_auth/lib/celest_cloud_auth.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:celest_core/_internal.dart';
1818
import 'package:meta/meta.dart';
1919
import 'package:shelf/shelf.dart';
2020

21+
export 'package:celest_cloud_auth/src/authentication/authentication_service.dart';
2122
export 'package:celest_cloud_auth/src/database/auth_database.dart';
2223
export 'package:celest_cloud_auth/src/email/email_provider.dart';
2324
export 'package:celest_cloud_auth/src/otp/otp_provider.dart';

services/celest_cloud_auth/lib/src/authentication/authentication_service.dart

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:typed_data';
2+
13
import 'package:cedar/cedar.dart';
24
// ignore: invalid_use_of_internal_member
35
import 'package:celest/src/runtime/http/cloud_middleware.dart';
@@ -253,13 +255,17 @@ extension type AuthenticationService._(_Deps _deps) implements Object {
253255
}
254256

255257
Future<SessionStateSuccess> _authenticateUser({
258+
required TypeId<Session> sessionId,
256259
required String userId,
257260
}) async {
258261
final user = await _db.getUser(userId: userId);
259262
if (user == null) {
260263
throw const NotFoundException('User not found');
261264
}
262-
final cork = await _corks.createUserCork(user: user);
265+
final cork = await _corks.createUserCork(
266+
user: user,
267+
sessionId: sessionId,
268+
);
263269
return SessionStateSuccess(
264270
cork: cork,
265271
user: user,
@@ -268,6 +274,7 @@ extension type AuthenticationService._(_Deps _deps) implements Object {
268274
}
269275

270276
Future<SessionStateSuccess> _createUser({
277+
required TypeId<Session> sessionId,
271278
required AuthenticationFactor factor,
272279
}) async {
273280
final (user, cork) = await _db.transaction(() async {
@@ -289,7 +296,10 @@ extension type AuthenticationService._(_Deps _deps) implements Object {
289296
roles: const [EntityUid.of('Celest::Role', 'authenticated')],
290297
),
291298
);
292-
final cork = await _corks.createUserCork(user: user);
299+
final cork = await _corks.createUserCork(
300+
user: user,
301+
sessionId: sessionId,
302+
);
293303
return (user, cork);
294304
});
295305
return SessionStateSuccess(
@@ -350,10 +360,14 @@ extension type AuthenticationService._(_Deps _deps) implements Object {
350360
);
351361
}
352362
if (session.userId case final userId?) {
353-
return _authenticateUser(userId: userId);
363+
return _authenticateUser(
364+
userId: userId,
365+
sessionId: session.sessionId,
366+
);
354367
}
355368
return _createUser(
356369
factor: factor,
370+
sessionId: session.sessionId,
357371
);
358372
}
359373

@@ -477,26 +491,35 @@ extension type AuthenticationService._(_Deps _deps) implements Object {
477491

478492
@visibleForTesting
479493
Future<void> endSession({
480-
required TypeId<Session> sessionId,
494+
required String sessionId,
481495
required String sessionToken,
482496
}) async {
483-
final session = await _db.authDrift
484-
.getSession(sessionId: sessionId.encoded)
485-
.getSingleOrNull();
486-
if (session == null) {
487-
throw const NotFoundException('Session not found');
497+
if (sessionId.isNotEmpty) {
498+
final session = await _db.authDrift
499+
.getSession(sessionId: sessionId)
500+
.getSingleOrNull();
501+
if (session == null) {
502+
throw const NotFoundException('Session not found');
503+
}
504+
sessionId = session.sessionId.encoded;
505+
} else {
506+
final sessionCork = CedarCork.parse(sessionToken);
507+
await _corks.verify(cork: sessionCork);
508+
sessionId = TypeId.decode(String.fromCharCodes(sessionCork.id)).encoded;
488509
}
489-
final sessionCork = CedarCork.parse(sessionToken);
490-
await _corks.verify(cork: sessionCork);
491-
492-
await _db.authDrift.deleteSession(sessionId: sessionId.encoded);
510+
await _db.transaction(() async {
511+
await _db.authDrift.deleteSession(sessionId: sessionId);
512+
await _db.authDrift.deleteCork(
513+
corkId: Uint8List.fromList(sessionId.codeUnits),
514+
);
515+
});
493516
}
494517

495518
Future<Response> handleEndSession(Request request) async {
496519
final jsonRequest = await JsonUtf8.decodeStream(request.read());
497520
final pbRequest = pb.EndSessionRequest()..mergeFromProto3Json(jsonRequest);
498521
await endSession(
499-
sessionId: TypeId.decode(pbRequest.sessionId),
522+
sessionId: pbRequest.sessionId,
500523
sessionToken: pbRequest.sessionToken,
501524
);
502525
final response = pb.EndSessionResponse(

services/celest_cloud_auth/lib/src/authorization/corks_repository.dart

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,33 +42,47 @@ extension type CorksRepository._(_Dependencies _deps) implements Object {
4242

4343
/// Creates a new cork for the given [user].
4444
Future<Cork> createUserCork({
45+
// TODO(dnys1): Make this required
46+
TypeId<Session>? sessionId,
4547
required User user,
4648
EntityUid? audience,
4749
}) async {
48-
final corkId = TypeId<Cork>().uuid.value;
50+
sessionId ??= TypeId<Session>();
51+
audience ??= issuer;
52+
final cryptoKey = await _cryptoKeys.getOrMintHmacKey(
53+
cryptoKeyId: sessionId.uuid.value,
54+
);
55+
final userId = EntityUid.of('Celest::User', user.userId);
56+
final expireTime = DateTime.timestamp().add(const Duration(days: 30));
4957
final userEntity = Entity(
50-
uid: EntityUid.of('Celest::User', user.userId),
58+
uid: userId,
5159
parents: [
5260
...user.roles,
5361
issuer,
5462
],
55-
attributes: RecordValue.fromJson(user.toJson()).attributes,
63+
attributes: {
64+
...RecordValue.fromJson(user.toJson()).attributes,
65+
'expireTime': Value.integer(expireTime.millisecondsSinceEpoch ~/ 1000),
66+
},
5667
);
57-
final corkBuilder = CedarCork.builder(corkId)
58-
..bearer = EntityUid.of('Celest::User', user.userId)
68+
final corkBuilder = CedarCork.builder(sessionId.uuid.value)
69+
..bearer = userId
70+
..audience = audience
5971
..issuer = issuer
60-
..audience = audience ?? issuer
6172
..claims = userEntity;
62-
final hmacKey = _cryptoKeys.rootKey;
63-
final cork = await corkBuilder.build().sign(hmacKey.signer);
73+
final cork = await corkBuilder.build().sign(cryptoKey.signer);
6474
await _db.transaction(() async {
6575
await _db.createEntity(userEntity);
66-
await _db.authDrift.createCork(
67-
corkId: corkId,
68-
cryptoKeyId: hmacKey.cryptoKeyId,
76+
await _db.authDrift.upsertCork(
77+
corkId: cork.id,
78+
cryptoKeyId: cryptoKey.cryptoKeyId,
6979
bearerType: 'Celest::User',
7080
bearerId: user.userId,
71-
expireTime: DateTime.timestamp().add(const Duration(days: 30)),
81+
audienceType: audience!.type,
82+
audienceId: audience.id,
83+
issuerType: issuer.type,
84+
issuerId: issuer.id,
85+
expireTime: expireTime,
7286
);
7387
});
7488
return cork;
@@ -97,7 +111,7 @@ extension type CorksRepository._(_Dependencies _deps) implements Object {
97111
),
98112
},
99113
);
100-
final corkBuilder = CedarCork.builder(cryptoKey.cryptoKeyId)
114+
final corkBuilder = CedarCork.builder(session.sessionId.uuid.value)
101115
..bearer = sessionUid
102116
..issuer = issuer
103117
..audience = issuer
@@ -110,6 +124,10 @@ extension type CorksRepository._(_Dependencies _deps) implements Object {
110124
cryptoKeyId: session.cryptoKeyId,
111125
bearerType: sessionUid.type,
112126
bearerId: sessionUid.id,
127+
audienceType: issuer.type,
128+
audienceId: issuer.id,
129+
issuerType: issuer.type,
130+
issuerId: issuer.id,
113131
expireTime: session.expireTime,
114132
);
115133
});

services/celest_cloud_auth/lib/src/database/schema/auth.drift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,39 @@ createCork:
315315
:expire_time
316316
);
317317

318+
upsertCork:
319+
INSERT INTO corks(
320+
cork_id,
321+
crypto_key_id,
322+
bearer_type,
323+
bearer_id,
324+
audience_type,
325+
audience_id,
326+
issuer_type,
327+
issuer_id,
328+
expire_time
329+
)
330+
VALUES (
331+
:cork_id,
332+
:crypto_key_id,
333+
:bearer_type,
334+
:bearer_id,
335+
:audience_type,
336+
:audience_id,
337+
:issuer_type,
338+
:issuer_id,
339+
:expire_time
340+
)
341+
ON CONFLICT(cork_id) DO UPDATE SET
342+
crypto_key_id = excluded.crypto_key_id,
343+
bearer_type = excluded.bearer_type,
344+
bearer_id = excluded.bearer_id,
345+
audience_type = excluded.audience_type,
346+
audience_id = excluded.audience_id,
347+
issuer_type = excluded.issuer_type,
348+
issuer_id = excluded.issuer_id,
349+
expire_time = excluded.expire_time;
350+
318351
-- Retrieves the cork for the given ID.
319352
getCork:
320353
SELECT * FROM corks

services/celest_cloud_auth/lib/src/database/schema/auth.drift.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,33 @@ class AuthDrift extends i7.ModularAccessor {
24412441
);
24422442
}
24432443

2444+
Future<int> upsertCork(
2445+
{required i2.Uint8List corkId,
2446+
required i2.Uint8List cryptoKeyId,
2447+
String? bearerType,
2448+
String? bearerId,
2449+
String? audienceType,
2450+
String? audienceId,
2451+
String? issuerType,
2452+
String? issuerId,
2453+
DateTime? expireTime}) {
2454+
return customInsert(
2455+
'INSERT INTO corks (cork_id, crypto_key_id, bearer_type, bearer_id, audience_type, audience_id, issuer_type, issuer_id, expire_time) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) ON CONFLICT (cork_id) DO UPDATE SET crypto_key_id = excluded.crypto_key_id, bearer_type = excluded.bearer_type, bearer_id = excluded.bearer_id, audience_type = excluded.audience_type, audience_id = excluded.audience_id, issuer_type = excluded.issuer_type, issuer_id = excluded.issuer_id, expire_time = excluded.expire_time',
2456+
variables: [
2457+
i0.Variable<i2.Uint8List>(corkId),
2458+
i0.Variable<i2.Uint8List>(cryptoKeyId),
2459+
i0.Variable<String>(bearerType),
2460+
i0.Variable<String>(bearerId),
2461+
i0.Variable<String>(audienceType),
2462+
i0.Variable<String>(audienceId),
2463+
i0.Variable<String>(issuerType),
2464+
i0.Variable<String>(issuerId),
2465+
i0.Variable<DateTime>(expireTime, const i5.TimestampType())
2466+
],
2467+
updates: {corks},
2468+
);
2469+
}
2470+
24442471
i0.Selectable<i3.Cork> getCork({required i2.Uint8List corkId}) {
24452472
return customSelect('SELECT * FROM corks WHERE cork_id = ?1', variables: [
24462473
i0.Variable<i2.Uint8List>(corkId)

services/celest_cloud_auth/test/authentication/authentication_service_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ void main() {
366366
check(() => session.sessionToken).returnsNormally();
367367
await check(
368368
tester.authenticationService.endSession(
369-
sessionId: session.sessionId,
369+
sessionId: session.sessionId.encoded,
370370
sessionToken: session.sessionToken,
371371
),
372372
).completes();
@@ -402,7 +402,7 @@ void main() {
402402

403403
await check(
404404
tester.authenticationService.endSession(
405-
sessionId: session.sessionId,
405+
sessionId: session.sessionId.encoded,
406406
sessionToken: cork.toString(),
407407
),
408408
).throws<InvalidSignatureException>();

services/celest_cloud_auth/test/tester.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:celest/src/runtime/serve.dart';
77
import 'package:celest_ast/celest_ast.dart';
88
import 'package:celest_cloud/celest_cloud.dart' show CelestCloud, ClientType;
99
import 'package:celest_cloud_auth/celest_cloud_auth.dart';
10-
import 'package:celest_cloud_auth/src/authentication/authentication_service.dart';
1110
import 'package:celest_cloud_auth/src/authorization/authorization_middleware.dart';
1211
import 'package:celest_cloud_auth/src/authorization/authorizer.dart';
1312
import 'package:celest_cloud_auth/src/authorization/cedar_interop.dart';

0 commit comments

Comments
 (0)