diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c3ea016..f59feebb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,12 @@ "dart.vmServiceLogFile": ".vscode/logs/vm_service/${name}.log", "dart.extensionLogFile": ".vscode/logs/dartcode_ext.log", "dart.analyzerInstrumentationLogFile": ".vscode/logs/analyzer/instrumentation.log", + "files.associations": { + "*.drift": "sql" + }, "yaml.schemas": { - "https://json.schemastore.org/pubspec.json": "file:///Users/dillonnys/celest/cloud/celest/examples/tasks/pubspec.yaml" - } + "https://json.schemastore.org/pubspec.json": "${workspaceRoot}/celest/examples/tasks/pubspec.yaml" + }, + // Don't timeout while debugging tests + "dart.testAdditionalArgs": ["--timeout=none"] } \ No newline at end of file diff --git a/melos.yaml b/melos.yaml index 448a3268..5decb7dd 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,6 +3,7 @@ name: celest_dev packages: - examples/** - packages/** + - services/** ignore: - "examples/**/celest" - "packages/**/example/celest" diff --git a/packages/celest/lib/src/auth/auth_provider.dart b/packages/celest/lib/src/auth/auth_provider.dart index 0d424f92..a0ea9a5d 100644 --- a/packages/celest/lib/src/auth/auth_provider.dart +++ b/packages/celest/lib/src/auth/auth_provider.dart @@ -1,11 +1,14 @@ +import 'package:celest/celest.dart'; import 'package:celest/src/config/config_values.dart'; /// {@template celest.auth.auth_provider} /// An authentication provider which can be used to sign in to Celest. /// /// Currently, Celest supports the following authentication methods: +/// /// - [AuthProvider.email] Email sign-in with OTP codes. -/// - [AuthProvider.sms] SMS sign-in with OTP codes. +/// - [ExternalAuthProvider.firebase] Firebase as an external identity provider. +/// - [ExternalAuthProvider.supabase] Supabase as an external identity provider. /// {@endtemplate} sealed class AuthProvider { /// {@macro celest.auth.auth_provider} @@ -13,36 +16,6 @@ sealed class AuthProvider { /// A provider which enables email sign-in with OTP codes. const factory AuthProvider.email() = _EmailAuthProvider; - - /// A provider which enables SMS sign-in with OTP codes. - const factory AuthProvider.sms() = _SmsAuthProvider; - - /// A provider which enables GitHub sign-in. - /// - /// [clientId] and [clientsecret] are required to authenticate with GitHub. - const factory AuthProvider.gitHub({ - required secret clientId, - required secret clientsecret, - }) = _GitHubAuthProvider; - - /// A provider which enables Google sign-in. - /// - /// [clientId] and [clientsecret] are required to authenticate with Google. - const factory AuthProvider.google({ - required secret clientId, - required secret clientsecret, - }) = _GoogleAuthProvider; - - /// A provider which enables Sign In with Apple. - /// - /// [clientId], [teamId], [keyId], and [privateKey] are required to - /// authenticate with Apple. - const factory AuthProvider.apple({ - required secret clientId, - required secret teamId, - required secret keyId, - required secret privateKey, - }) = _AppleAuthProvider; } /// {@template celest.auth.external_auth_provider} @@ -81,44 +54,6 @@ final class _EmailAuthProvider extends AuthProvider { const _EmailAuthProvider(); } -final class _SmsAuthProvider extends AuthProvider { - const _SmsAuthProvider(); -} - -final class _GitHubAuthProvider extends AuthProvider { - const _GitHubAuthProvider({ - required this.clientId, - required this.clientsecret, - }); - - final secret clientId; - final secret clientsecret; -} - -final class _GoogleAuthProvider extends AuthProvider { - const _GoogleAuthProvider({ - required this.clientId, - required this.clientsecret, - }); - - final secret clientId; - final secret clientsecret; -} - -final class _AppleAuthProvider extends AuthProvider { - const _AppleAuthProvider({ - required this.clientId, - required this.teamId, - required this.keyId, - required this.privateKey, - }); - - final secret clientId; - final secret teamId; - final secret keyId; - final secret privateKey; -} - final class _FirebaseExternalAuthProvider extends ExternalAuthProvider { const _FirebaseExternalAuthProvider({ // ignore: unused_element diff --git a/packages/celest/lib/src/core/context.dart b/packages/celest/lib/src/core/context.dart index b8420d1d..46b429c9 100644 --- a/packages/celest/lib/src/core/context.dart +++ b/packages/celest/lib/src/core/context.dart @@ -18,9 +18,10 @@ import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf_router/shelf_router.dart'; /// The [Context] for the current request. -Context get context => Context.current; +Context get context => Context._current ?? Context._(Zone.current).parent!; /// {@template celest.runtime.celest_context} /// A per-request context object which propogates request information and common @@ -51,11 +52,11 @@ final class Context { /// Sets the root [Context] for the current execution scope. /// /// This is only allowed in tests. - @visibleForTesting + @internal static set root(Context value) { - if (!kDebugMode) { + if (_root != null && kReleaseMode) { throw UnsupportedError( - 'Setting the root context is only allowed in tests', + 'Cannot set the root context after it has already been set', ); } _root = value; @@ -64,6 +65,8 @@ final class Context { /// The [Context] for the current execution scope. static Context get current => Context.of(Zone.current); + static Context? get _current => _contexts[Zone.current]; + /// Context-specific values. final Map, Object> _values = {}; @@ -163,6 +166,14 @@ final class Context { /// The logger for the current context. Logger get logger => get(ContextKey.logger) ?? Logger.root; + /// Logs a message at the [Level.INFO] level. + void log(String message) { + logger.info(message); + } + + /// The [Router] for the current context. + Router get router => expect(ContextKey.router); + (Context, V)? _get(ContextKey key) { if (key.read(this) case final value?) { return (this, value); @@ -187,8 +198,9 @@ final class Context { } /// Sets the value of [key] in the current [Context]. - void put(ContextKey key, V value) { + V put(ContextKey key, V value) { key.set(this, value); + return value; } /// Sets the value of [key] in this [Context] if it is not already set. @@ -219,6 +231,21 @@ final class Context { V? remove(ContextKey key) { return _values.remove(key) as V?; } + + final List _postRequestCallbacks = []; + + /// Registers a callback to be run after the current context completes. + void after( + FutureOr Function(shelf.Response) callback, { + FutureOr Function(Object e, StackTrace st)? onError, + }) { + _postRequestCallbacks.add( + ( + onResponse: _zone.bindUnaryCallback((response) => callback(response)), + onError: onError == null ? null : _zone.bindBinaryCallback(onError), + ), + ); + } } /// {@template celest.runtime.context_key} @@ -255,6 +282,12 @@ abstract interface class ContextKey { /// The context key for the context [ResolvedProject]. static const ContextKey project = ContextKey('project'); + /// The context key for the global [Router]. + /// + /// This is used to register routes for the current service and can be used + /// to dynamically add/remove routes at runtime. + static const ContextKey router = ContextKey('router'); + /// Reads the value for `this` from the given [context]. V? read(Context context); @@ -301,3 +334,32 @@ final class _ContextKey implements ContextKey { final class _PrincipalContextKey extends _ContextKey { const _PrincipalContextKey() : super('principal'); } + +/// A callback to be run after the current context completes. +typedef PostRequestCallback = ({ + FutureOr Function(shelf.Response) onResponse, + FutureOr Function(Object e, StackTrace st)? onError, +}); + +/// {@template celest.runtime.post_request_callbacks} +/// The context key for post-request callbacks. +/// {@endtemplate} +final class PostRequestCallbacks + implements ContextKey> { + /// {@macro celest.runtime.post_request_callbacks} + const PostRequestCallbacks(); + + @override + List read(Context context) { + return context._postRequestCallbacks; + } + + @override + void set(Context context, List? value) { + if (value == null) { + context._postRequestCallbacks.clear(); + } else { + context._postRequestCallbacks.addAll(value); + } + } +} diff --git a/packages/celest/lib/src/functions/http/http_status.dart b/packages/celest/lib/src/functions/http/http_status.dart index a0906a74..30d936e7 100644 --- a/packages/celest/lib/src/functions/http/http_status.dart +++ b/packages/celest/lib/src/functions/http/http_status.dart @@ -9,7 +9,9 @@ extension type const HttpStatus._(int code) implements int { 'code must be in the range 100-999', ); - /// `200 OK` The request succeeded. + /// `200 OK` + /// + /// The request succeeded. /// /// The result meaning of "success" depends on the HTTP method: /// @@ -21,67 +23,135 @@ extension type const HttpStatus._(int code) implements int { /// transmitted in the message body. /// - `TRACE`: The message body contains the request message as received by /// the server. - static const ok = HttpStatus(200); + static const HttpStatus ok = HttpStatus(200); - /// `201 Created` The request succeeded, and a new resource was created as a + /// `201 Created` + /// + /// The request succeeded, and a new resource was created as a /// result. /// /// This is typically the response sent after `POST` requests, or some `PUT` /// requests. - static const created = HttpStatus(201); + static const HttpStatus created = HttpStatus(201); - /// `202 Accepted` The request has been received but not yet acted upon. + /// `202 Accepted` + /// + /// The request has been received but not yet acted upon. /// /// It is noncommittal, since there is no way in HTTP to later send an /// asynchronous response indicating the outcome of the request. It is /// intended for cases where another process or server handles the request, /// or for batch processing. - static const accepted = HttpStatus(202); + static const HttpStatus accepted = HttpStatus(202); - /// `203 Non-Authoritative Information` This response code means the returned metadata is not exactly the - /// same as is available from the origin server, but is collected from a local - /// or a third-party copy. + /// `203 Non-Authoritative Information` + /// + /// This response code means the returned metadata is not exactly the same as + /// is available from the origin server, but is collected from a local or a + /// third-party copy. /// /// This is mostly used for mirrors or backups of another resource. Except /// for that specific case, the `200` [ok] response is preferred to this /// status. - static const nonAuthoritativeInformation = HttpStatus(203); + static const HttpStatus nonAuthoritativeInformation = HttpStatus(203); - /// `204 No Content` There is no content to send for this request, but the headers may be + /// `204 No Content` + /// + /// There is no content to send for this request, but the headers may be /// useful. /// /// The user agent may update its cached headers for this resource with the /// new ones. - static const noContent = HttpStatus(204); + static const HttpStatus noContent = HttpStatus(204); + + /// `205 Reset Content` + /// + /// Tells the user agent to reset the document which sent this request. + static const HttpStatus resetContent = HttpStatus(205); + + /// `206 Partial Content` + /// + /// This response code is used when the `Range` header is sent from the client + /// to request only part of a resource. + static const HttpStatus partialContent = HttpStatus(206); + + /// `300 Multiple Choices` + /// + /// The request has more than one possible response. The user-agent or user + /// should choose one of them. There is no standardized way to choose one of + /// the responses. + static const HttpStatus multipleChoices = HttpStatus(300); + + /// `301 Moved Permanently` + /// + /// This response code means that the URI of the requested resource has been + /// changed permanently. The new URL is given in the response. + static const HttpStatus movedPermanently = HttpStatus(301); + + /// `302 Found` + /// + /// This response code means that the URI of requested resource has been + /// changed _temporarily_. Further changes in the URI might be made in the + /// future. Therefore, this same URI should be used by the client in future + /// requests. + static const HttpStatus found = HttpStatus(302); + + /// `303 See Other` + /// + /// The server sent this response to direct the client to get the requested + /// resource at another URI with a `GET` request. + static const HttpStatus seeOther = HttpStatus(303); - /// `205 Reset Content` Tells the user agent to reset the document which sent - /// this request. - static const resetContent = HttpStatus(205); + /// `304 Not Modified` + /// + /// This is used for caching purposes. It tells the client that the response + /// has not been modified, so the client can continue to use the same cached + /// version of the response. + static const HttpStatus notModified = HttpStatus(304); + + /// `307 Temporary Redirect` + /// + /// The server sends this response to direct the client to get the requested + /// resource at another URI with the same method that was used in the prior + /// request. + /// + /// This has the same semantics as the `302` [found] HTTP response code, with + /// the exception that the user agent must not change the HTTP method used: + /// If a `POST` was used in the first request, a `POST` must be used in the + /// second request. + static const HttpStatus temporaryRedirect = HttpStatus(307); - /// `206 Partial Content` This response code is used when the Range header is - /// sent from the client to request only part of a resource. - static const partialContent = HttpStatus(206); + /// `308 Permanent Redirect` + /// + /// This means that the resource is now permanently located at another URI, + /// specified by the `Location` headers. + /// + /// This has the same semantics as the `301` [movedPermanently] HTTP response + /// code, with the exception that the user agent must not change the HTTP + /// method used: If a `POST` was used in the first request, a `POST` must be + /// used in the second request. + static const HttpStatus permanentRedirect = HttpStatus(308); /// `400 Bad Request` /// /// The server cannot or will not process the request due to something that is /// perceived to be a client error (e.g., malformed request syntax, invalid /// request message framing, or deceptive request routing). - static const badRequest = HttpStatus(400); + static const HttpStatus badRequest = HttpStatus(400); /// `401 Unauthorized` /// /// Although the HTTP standard specifies "unauthorized", semantically this /// response means "unauthenticated". That is, the client must authenticate /// itself to get the requested response. - static const unauthorized = HttpStatus(401); + static const HttpStatus unauthorized = HttpStatus(401); /// `403 Forbidden` /// /// The client does not have access rights to the content; that is, it is /// unauthorized, so the server is refusing to give the requested resource. /// Unlike `401` [unauthorized], the client's identity is known to the server. - static const forbidden = HttpStatus(403); + static const HttpStatus forbidden = HttpStatus(403); /// `404 Not Found` /// @@ -90,21 +160,21 @@ extension type const HttpStatus._(int code) implements int { /// is valid but the resource itself does not exist. Servers may also send /// this response instead of `403` [forbidden] to hide the existence of a /// resource from an unauthorized client. - static const notFound = HttpStatus(404); + static const HttpStatus notFound = HttpStatus(404); /// `405 Method Not Allowed` /// /// The request method is known by the server but is not supported by the /// target resource. For example, an API may not allow calling `DELETE` to /// remove a resource. - static const methodNotAllowed = HttpStatus(405); + static const HttpStatus methodNotAllowed = HttpStatus(405); /// `406 Not Acceptable` /// /// This response is sent when the web server, after performing server-driven /// content negotiation, doesn't find any content that conforms to the /// criteria given by the user agent. - static const notAcceptable = HttpStatus(406); + static const HttpStatus notAcceptable = HttpStatus(406); /// `408 Request Timeout` /// @@ -114,13 +184,13 @@ extension type const HttpStatus._(int code) implements int { /// some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection /// mechanisms to speed up surfing. Also note that some servers merely shut /// down the connection without sending this message. - static const requestTimeout = HttpStatus(408); + static const HttpStatus requestTimeout = HttpStatus(408); /// `409 Conflict` /// /// This response is sent when a request conflicts with the current state of /// the server. - static const conflict = HttpStatus(409); + static const HttpStatus conflict = HttpStatus(409); /// `410 Gone` /// @@ -130,68 +200,68 @@ extension type const HttpStatus._(int code) implements int { /// intends this status code to be used for "limited-time, promotional /// services". APIs should not feel compelled to indicate resources that have /// been deleted with this status code. - static const gone = HttpStatus(410); + static const HttpStatus gone = HttpStatus(410); /// `411 Length Required` /// /// Server rejected the request because the Content-Length header field is /// not defined and the server requires it. - static const lengthRequired = HttpStatus(411); + static const HttpStatus lengthRequired = HttpStatus(411); /// `412 Precondition Failed` /// /// The client has indicated preconditions in its headers which the server /// does not meet. - static const preconditionFailed = HttpStatus(412); + static const HttpStatus preconditionFailed = HttpStatus(412); /// `413 Payload Too Large` /// /// Request entity is larger than limits defined by server; the server might /// close the connection or return a `Retry-After` header field. - static const payloadTooLarge = HttpStatus(413); + static const HttpStatus payloadTooLarge = HttpStatus(413); /// `414 URI Too Long` /// /// The URI requested by the client is longer than the server is willing to /// interpret. - static const uriTooLong = HttpStatus(414); + static const HttpStatus uriTooLong = HttpStatus(414); /// `415 Unsupported Media Type` /// /// The media format of the requested data is not supported by the server, so /// the server is rejecting the request. - static const unsupportedMediaType = HttpStatus(415); + static const HttpStatus unsupportedMediaType = HttpStatus(415); /// `416 Range Not Satisfiable` /// /// The range specified by the `Range` header field in the request can't be /// fulfilled; it's possible that the range is outside the size of the target /// URI's data. - static const rangeNotSatisfiable = HttpStatus(416); + static const HttpStatus rangeNotSatisfiable = HttpStatus(416); /// `429 Too Many Requests` /// /// The user has sent too many requests in a given amount of time ("rate /// limiting"). - static const tooManyRequests = HttpStatus(429); + static const HttpStatus tooManyRequests = HttpStatus(429); /// `500 Internal Server Error` /// /// The server has encountered a situation it doesn't know how to handle. - static const internalServerError = HttpStatus(500); + static const HttpStatus internalServerError = HttpStatus(500); /// `501 Not Implemented` /// /// The request method is not supported by the server and cannot be handled. /// The only methods that servers are required to support (and therefore that /// must not return this code) are `GET` and `HEAD`. - static const notImplemented = HttpStatus(501); + static const HttpStatus notImplemented = HttpStatus(501); /// `502 Bad Gateway` /// /// This error response means that the server, while working as a gateway to /// get a response needed to handle the request, got an invalid response. - static const badGateway = HttpStatus(502); + static const HttpStatus badGateway = HttpStatus(502); /// `503 Service Unavailable` /// @@ -201,11 +271,11 @@ extension type const HttpStatus._(int code) implements int { /// sent. This responses should be used for temporary conditions and the /// `Retry-After` HTTP header should, if possible, contain the estimated time /// before the recovery of the service. - static const serviceUnavailable = HttpStatus(503); + static const HttpStatus serviceUnavailable = HttpStatus(503); /// `504 Gateway Timeout` /// /// This error response is given when the server is acting as a gateway and /// cannot get a response in time. - static const gatewayTimeout = HttpStatus(504); + static const HttpStatus gatewayTimeout = HttpStatus(504); } diff --git a/packages/celest/lib/src/runtime/auth/firebase/firebase_token_verifier.dart b/packages/celest/lib/src/runtime/auth/firebase/firebase_token_verifier.dart index 38080ae4..a954edc5 100644 --- a/packages/celest/lib/src/runtime/auth/firebase/firebase_token_verifier.dart +++ b/packages/celest/lib/src/runtime/auth/firebase/firebase_token_verifier.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:celest/src/runtime/auth/firebase/firebase_public_key_store.dart'; import 'package:celest/src/runtime/auth/jwt/base64_raw_url.dart'; -import 'package:celest_core/celest_core.dart' show CloudException, User; +import 'package:celest_core/celest_core.dart' show CloudException, Email, User; import 'package:crypto_keys/crypto_keys.dart' show AlgorithmIdentifier, Signature; @@ -77,8 +77,15 @@ final class FirebaseTokenVerifier { return User( userId: claims['sub'] as String, - email: claims['email'] as String?, - emailVerified: claims['email_verified'] as bool?, + emails: switch (claims['email'] as String?) { + final email? => [ + Email( + email: email, + isVerified: claims['email_verified'] as bool? ?? false, + ), + ], + _ => [], + }, ); } } diff --git a/packages/celest/lib/src/runtime/auth/supabase/supabase_token_verifier.dart b/packages/celest/lib/src/runtime/auth/supabase/supabase_token_verifier.dart index 99be4f8f..42620f65 100644 --- a/packages/celest/lib/src/runtime/auth/supabase/supabase_token_verifier.dart +++ b/packages/celest/lib/src/runtime/auth/supabase/supabase_token_verifier.dart @@ -70,7 +70,10 @@ final class SupabaseTokenVerifier { }; return User( userId: claims['sub'] as String, - email: claims['email'] as String?, + emails: switch (claims['email'] as String?) { + final email? => [Email(email: email)], + _ => const [], + }, ); } @@ -99,8 +102,9 @@ final class SupabaseTokenVerifier { (user['email_confirmed_at'] ?? user['confirmed_at']) != null; return User( userId: userId, - email: email, - emailVerified: emailVerified, + emails: email != null + ? [Email(email: email, isVerified: emailVerified)] + : const [], ); } // Shouldn't ever happened for a well-formed response. diff --git a/packages/celest/lib/src/runtime/gcp/gcp.dart b/packages/celest/lib/src/runtime/gcp/gcp.dart index 1d61d832..f9d726e4 100644 --- a/packages/celest/lib/src/runtime/gcp/gcp.dart +++ b/packages/celest/lib/src/runtime/gcp/gcp.dart @@ -2,7 +2,9 @@ library; import 'package:celest/src/core/context.dart'; +import 'package:celest_core/_internal.dart'; import 'package:google_cloud/google_cloud.dart'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; /// The context key for the active GCP project ID. @@ -13,7 +15,10 @@ const ContextKey googleCloudProjectKey = ContextKey(); Future googleCloudProject() async { try { return await computeProjectId(); - } on Exception { + } on Object catch (e, st) { + if (kReleaseMode) { + Logger.root.warning('Failed to get GCP project ID', e, st); + } return null; } } diff --git a/packages/celest/lib/src/runtime/http/cloud_middleware.dart b/packages/celest/lib/src/runtime/http/cloud_middleware.dart index edbabf33..c0bdb87f 100644 --- a/packages/celest/lib/src/runtime/http/cloud_middleware.dart +++ b/packages/celest/lib/src/runtime/http/cloud_middleware.dart @@ -91,7 +91,7 @@ final class CloudExceptionMiddleware implements Middleware { }, body: JsonUtf8.encode({ '@status': { - 'code': e.type, + 'code': e.code, 'message': e.message, 'details': [ { @@ -175,6 +175,7 @@ final class RootMiddleware implements Middleware { return (request) async { final completer = Completer.sync(); + late Context requestContext; final requestZone = Zone.current.fork( specification: ZoneSpecification( handleUncaughtError: (self, parent, zone, error, stackTrace) { @@ -200,7 +201,7 @@ final class RootMiddleware implements Middleware { ); requestZone.runGuarded( () async { - Context.current + requestContext = Context.current ..put(ContextKey.currentRequest, request) ..put(ContextKey.currentTrace, request.trace); final response = await inner(request); @@ -210,9 +211,64 @@ final class RootMiddleware implements Middleware { }, ); - return completer.future; + try { + final response = await completer.future; + await _postRequest(requestContext, response); + return response; + } on Object catch (error, stackTrace) { + await _postRequestError(requestContext, error, stackTrace); + rethrow; + } }; } + + Future _postRequest(Context context, Response response) async { + final callbacks = context.get(const PostRequestCallbacks()); + if (callbacks case final callbacks? when callbacks.isNotEmpty) { + for (final callback in callbacks.reversed) { + try { + if (callback.onResponse(response) case final Future future) { + await future; + } + } on Object catch (e, st) { + context.logger.shout( + 'An error occurred while running a post-request callback', + e, + st, + ); + } + } + callbacks.clear(); + } + } + + Future _postRequestError( + Context context, + Object error, + StackTrace stackTrace, + ) async { + final callbacks = context.get(const PostRequestCallbacks()); + if (callbacks case final callbacks? when callbacks.isNotEmpty) { + for (final callback in callbacks.reversed) { + final onError = callback.onError; + if (onError == null) { + continue; + } + try { + if (onError(error, stackTrace) case final Future future) { + await future; + } + } on Object catch (e, st) { + context.logger.shout( + 'An error occurred while running a post-request callback', + e, + st, + ); + } + } + callbacks.clear(); + } + } } /// Standard header used by diff --git a/packages/celest/lib/src/runtime/serve.dart b/packages/celest/lib/src/runtime/serve.dart index b5fa671d..c434d245 100644 --- a/packages/celest/lib/src/runtime/serve.dart +++ b/packages/celest/lib/src/runtime/serve.dart @@ -42,7 +42,10 @@ Future serve({ required Map targets, ast.ResolvedProject? config, FutureOr Function(Context context)? setup, + int? port, }) async { + Context.root = Context.of(Zone.current); + await configure( config: config, ); @@ -50,6 +53,9 @@ Future serve({ if (projectId != null) { Context.root.put(googleCloudProjectKey, projectId); } + + final router = Router(); + Context.root.put(ContextKey.router, router); if (setup != null) { try { await setup(Context.root); @@ -58,16 +64,17 @@ Future serve({ rethrow; } } - final router = Router()..get('/v1/healthz', (_) => Response.ok('OK')); for (final MapEntry(key: route, value: target) in targets.entries) { - target._apply(router, route); + target.apply(router, route); } + router.get('/v1/healthz', (_) => Response.ok('OK')); + final pipeline = const Pipeline() .addMiddleware(const RootMiddleware().call) .addMiddleware(const CorsMiddleware().call) .addMiddleware(const CloudExceptionMiddleware().call) .addHandler(router.call); - final port = switch (Platform.environment['PORT']) { + port ??= switch (Platform.environment['PORT']) { final port? => int.tryParse(port) ?? (throw StateError('Invalid PORT set: "$port"')), _ => defaultCelestPort, @@ -76,10 +83,9 @@ Future serve({ pipeline, InternetAddress.anyIPv4, port, - shared: true, poweredByHeader: 'Celest, the Flutter cloud platform', ); - print('Serving on http://localhost:$port'); + print('Serving on http://localhost:${server.port}'); unawaited( StreamGroup.merge([ ProcessSignal.sigint.watch(), diff --git a/packages/celest/lib/src/runtime/targets.dart b/packages/celest/lib/src/runtime/targets.dart index 9e31951f..45c7c667 100644 --- a/packages/celest/lib/src/runtime/targets.dart +++ b/packages/celest/lib/src/runtime/targets.dart @@ -20,7 +20,8 @@ abstract base class CloudFunctionTarget { /// This is called once when the target is instantiated. void init() {} - void _apply(Router router, String route); + /// Applies this target to a [Router] at a given [route]. + void apply(Router router, String route); } /// {@template celest.runtime.cloud_function_http_target} @@ -40,7 +41,7 @@ abstract base class CloudFunctionHttpTarget extends CloudFunctionTarget { String get method => 'POST'; @override - void _apply(Router router, String route) { + void apply(Router router, String route) { var pipeline = const Pipeline(); for (final middleware in middlewares) { pipeline = pipeline.addMiddleware(middleware.call); @@ -62,7 +63,7 @@ abstract base class CloudFunctionHttpTarget extends CloudFunctionTarget { /// {@endtemplate} abstract base class CloudEventSourceTarget extends CloudFunctionTarget { @override - void _apply(Router router, String route) { + void apply(Router router, String route) { var pipeline = const Pipeline(); for (final middleware in middlewares) { pipeline = pipeline.addMiddleware(middleware.call); diff --git a/packages/celest/test/runtime/configuration_test.dart b/packages/celest/test/runtime/configuration_test.dart index b610bdd6..4c083840 100644 --- a/packages/celest/test/runtime/configuration_test.dart +++ b/packages/celest/test/runtime/configuration_test.dart @@ -36,8 +36,8 @@ final testProject = ResolvedProject( secrets: ['HELLO_WORLD_SECRET'], streamConfig: null, httpConfig: ResolvedHttpConfig( - method: 'POST', route: ResolvedHttpRoute( + method: 'POST', path: '/greeting/hello-world', ), status: 200, diff --git a/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart b/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart index bdbaba3a..1dd00631 100644 --- a/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart +++ b/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart @@ -468,20 +468,20 @@ class ResolvedFunction extends $pb.GeneratedMessage { /// The HTTP configuration of a [ResolvedFunction][]. class ResolvedHttpConfig extends $pb.GeneratedMessage { factory ResolvedHttpConfig({ - $core.String? method, - ResolvedHttpRoute? route, $core.int? status, + ResolvedHttpRoute? route, + $core.Iterable? additionalRoutes, $core.Map<$core.String, $core.int>? statusMappings, }) { final $result = create(); - if (method != null) { - $result.method = method; + if (status != null) { + $result.status = status; } if (route != null) { $result.route = route; } - if (status != null) { - $result.status = status; + if (additionalRoutes != null) { + $result.additionalRoutes.addAll(additionalRoutes); } if (statusMappings != null) { $result.statusMappings.addAll(statusMappings); @@ -500,10 +500,12 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedHttpConfig', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'method') + ..a<$core.int>(1, _omitFieldNames ? '' : 'status', $pb.PbFieldType.O3) ..aOM(2, _omitFieldNames ? '' : 'route', subBuilder: ResolvedHttpRoute.create) - ..a<$core.int>(3, _omitFieldNames ? '' : 'status', $pb.PbFieldType.O3) + ..pc( + 3, _omitFieldNames ? '' : 'additionalRoutes', $pb.PbFieldType.PM, + subBuilder: ResolvedHttpRoute.create) ..m<$core.String, $core.int>(4, _omitFieldNames ? '' : 'statusMappings', entryClassName: 'ResolvedHttpConfig.StatusMappingsEntry', keyFieldType: $pb.PbFieldType.OS, @@ -534,18 +536,18 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(create); static ResolvedHttpConfig? _defaultInstance; - /// The HTTP method of the function. + /// The successful status code of the function. @$pb.TagNumber(1) - $core.String get method => $_getSZ(0); + $core.int get status => $_getIZ(0); @$pb.TagNumber(1) - set method($core.String v) { - $_setString(0, v); + set status($core.int v) { + $_setSignedInt32(0, v); } @$pb.TagNumber(1) - $core.bool hasMethod() => $_has(0); + $core.bool hasStatus() => $_has(0); @$pb.TagNumber(1) - void clearMethod() => clearField(1); + void clearStatus() => clearField(1); /// The resolved route configuration of the function. @$pb.TagNumber(2) @@ -562,18 +564,9 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { @$pb.TagNumber(2) ResolvedHttpRoute ensureRoute() => $_ensure(1); - /// The successful status code of the function. - @$pb.TagNumber(3) - $core.int get status => $_getIZ(2); + /// Additional route configurations of the function. @$pb.TagNumber(3) - set status($core.int v) { - $_setSignedInt32(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasStatus() => $_has(2); - @$pb.TagNumber(3) - void clearStatus() => clearField(3); + $core.List get additionalRoutes => $_getList(2); /// A mapping of Dart types to HTTP status codes. /// @@ -591,9 +584,13 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { /// A route to an HTTP endpoint. class ResolvedHttpRoute extends $pb.GeneratedMessage { factory ResolvedHttpRoute({ + $core.String? method, $core.String? path, }) { final $result = create(); + if (method != null) { + $result.method = method; + } if (path != null) { $result.path = path; } @@ -611,7 +608,8 @@ class ResolvedHttpRoute extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedHttpRoute', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'path') + ..aOS(1, _omitFieldNames ? '' : 'method') + ..aOS(2, _omitFieldNames ? '' : 'path') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -637,18 +635,31 @@ class ResolvedHttpRoute extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(create); static ResolvedHttpRoute? _defaultInstance; - /// The path of the HTTP endpoint. + /// The HTTP method of the route. @$pb.TagNumber(1) - $core.String get path => $_getSZ(0); + $core.String get method => $_getSZ(0); @$pb.TagNumber(1) - set path($core.String v) { + set method($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasPath() => $_has(0); + $core.bool hasMethod() => $_has(0); @$pb.TagNumber(1) - void clearPath() => clearField(1); + void clearMethod() => clearField(1); + + /// The path to the HTTP endpoint. + @$pb.TagNumber(2) + $core.String get path => $_getSZ(1); + @$pb.TagNumber(2) + set path($core.String v) { + $_setString(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasPath() => $_has(1); + @$pb.TagNumber(2) + void clearPath() => clearField(2); } /// The stream configuration of a [ResolvedFunction][]. diff --git a/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart b/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart index 3fc42dd3..02fd8cd5 100644 --- a/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart +++ b/packages/celest_ast/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart @@ -238,7 +238,7 @@ final $typed_data.Uint8List resolvedFunctionDescriptor = $convert.base64Decode( const ResolvedHttpConfig$json = { '1': 'ResolvedHttpConfig', '2': [ - {'1': 'method', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'method'}, + {'1': 'status', '3': 1, '4': 1, '5': 5, '8': {}, '10': 'status'}, { '1': 'route', '3': 2, @@ -248,7 +248,15 @@ const ResolvedHttpConfig$json = { '8': {}, '10': 'route' }, - {'1': 'status', '3': 3, '4': 1, '5': 5, '8': {}, '10': 'status'}, + { + '1': 'additional_routes', + '3': 3, + '4': 3, + '5': 11, + '6': '.celest.ast.v1.ResolvedHttpRoute', + '8': {}, + '10': 'additionalRoutes' + }, { '1': 'status_mappings', '3': 4, @@ -274,24 +282,27 @@ const ResolvedHttpConfig_StatusMappingsEntry$json = { /// Descriptor for `ResolvedHttpConfig`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedHttpConfigDescriptor = $convert.base64Decode( - 'ChJSZXNvbHZlZEh0dHBDb25maWcSGwoGbWV0aG9kGAEgASgJQgPgQQJSBm1ldGhvZBI7CgVyb3' + 'ChJSZXNvbHZlZEh0dHBDb25maWcSGwoGc3RhdHVzGAEgASgFQgPgQQJSBnN0YXR1cxI7CgVyb3' 'V0ZRgCIAEoCzIgLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWRIdHRwUm91dGVCA+BBAlIFcm91dGUS' - 'GwoGc3RhdHVzGAMgASgFQgPgQQJSBnN0YXR1cxJjCg9zdGF0dXNfbWFwcGluZ3MYBCADKAsyNS' - '5jZWxlc3QuYXN0LnYxLlJlc29sdmVkSHR0cENvbmZpZy5TdGF0dXNNYXBwaW5nc0VudHJ5QgPg' - 'QQJSDnN0YXR1c01hcHBpbmdzGkEKE1N0YXR1c01hcHBpbmdzRW50cnkSEAoDa2V5GAEgASgJUg' - 'NrZXkSFAoFdmFsdWUYAiABKAVSBXZhbHVlOgI4AQ=='); + 'UgoRYWRkaXRpb25hbF9yb3V0ZXMYAyADKAsyIC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkSHR0cF' + 'JvdXRlQgPgQQFSEGFkZGl0aW9uYWxSb3V0ZXMSYwoPc3RhdHVzX21hcHBpbmdzGAQgAygLMjUu' + 'Y2VsZXN0LmFzdC52MS5SZXNvbHZlZEh0dHBDb25maWcuU3RhdHVzTWFwcGluZ3NFbnRyeUID4E' + 'ECUg5zdGF0dXNNYXBwaW5ncxpBChNTdGF0dXNNYXBwaW5nc0VudHJ5EhAKA2tleRgBIAEoCVID' + 'a2V5EhQKBXZhbHVlGAIgASgFUgV2YWx1ZToCOAE='); @$core.Deprecated('Use resolvedHttpRouteDescriptor instead') const ResolvedHttpRoute$json = { '1': 'ResolvedHttpRoute', '2': [ - {'1': 'path', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'path'}, + {'1': 'method', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'method'}, + {'1': 'path', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'path'}, ], }; /// Descriptor for `ResolvedHttpRoute`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedHttpRouteDescriptor = $convert.base64Decode( - 'ChFSZXNvbHZlZEh0dHBSb3V0ZRIXCgRwYXRoGAEgASgJQgPgQQJSBHBhdGg='); + 'ChFSZXNvbHZlZEh0dHBSb3V0ZRIbCgZtZXRob2QYASABKAlCA+BBAlIGbWV0aG9kEhcKBHBhdG' + 'gYAiABKAlCA+BBAlIEcGF0aA=='); @$core.Deprecated('Use resolvedStreamConfigDescriptor instead') const ResolvedStreamConfig$json = { diff --git a/packages/celest_ast/lib/src/resolved_ast.dart b/packages/celest_ast/lib/src/resolved_ast.dart index ce4d2a91..fe5403aa 100644 --- a/packages/celest_ast/lib/src/resolved_ast.dart +++ b/packages/celest_ast/lib/src/resolved_ast.dart @@ -183,9 +183,9 @@ abstract class ResolvedCloudFunction required String functionId, required String apiId, required ResolvedHttpConfig httpConfig, - required ResolvedStreamConfig? streamConfig, - required Iterable variables, - required Iterable secrets, + ResolvedStreamConfig? streamConfig, + Iterable variables = const [], + Iterable secrets = const [], cedar.PolicySet? policySet, }) { return _$ResolvedCloudFunction._( @@ -267,15 +267,15 @@ abstract class ResolvedCloudFunction abstract class ResolvedHttpConfig implements Built { factory ResolvedHttpConfig({ - required String method, + int status = 200, required ResolvedHttpRoute route, - required int status, - required Map statusMappings, + Iterable additionalRoutes = const [], + Map statusMappings = const {}, }) { return _$ResolvedHttpConfig._( - method: method, - route: route, status: status, + route: route, + additionalRoutes: additionalRoutes.toBuiltList(), statusMappings: statusMappings.build(), ); } @@ -290,8 +290,8 @@ abstract class ResolvedHttpConfig factory ResolvedHttpConfig.fromProto(pb.ResolvedHttpConfig proto) { return ResolvedHttpConfig( - method: proto.method, route: ResolvedHttpRoute.fromProto(proto.route), + additionalRoutes: proto.additionalRoutes.map(ResolvedHttpRoute.fromProto), status: proto.status, statusMappings: { for (final entry in proto.statusMappings.entries) @@ -304,13 +304,11 @@ abstract class ResolvedHttpConfig @BuiltValueHook(finalizeBuilder: true) static void _defaults(ResolvedHttpConfigBuilder b) { - b - ..method ??= 'POST' - ..status ??= 200; + b.status ??= 200; } - String get method; ResolvedHttpRoute get route; + BuiltList get additionalRoutes; int get status; BuiltMap get statusMappings; @@ -324,8 +322,8 @@ abstract class ResolvedHttpConfig pb.ResolvedHttpConfig toProto() { return pb.ResolvedHttpConfig( - method: method, route: route.toProto(), + additionalRoutes: additionalRoutes.map((e) => e.toProto()), status: status, statusMappings: { for (final entry in statusMappings.entries) @@ -405,9 +403,11 @@ abstract class ResolvedStreamConfig abstract class ResolvedHttpRoute implements Built { factory ResolvedHttpRoute({ + String method = 'POST', required String path, }) { return _$ResolvedHttpRoute._( + method: method, path: path, ); } @@ -422,12 +422,19 @@ abstract class ResolvedHttpRoute factory ResolvedHttpRoute.fromProto(pb.ResolvedHttpRoute proto) { return ResolvedHttpRoute( + method: proto.method, path: proto.path, ); } + @BuiltValueHook(finalizeBuilder: true) + static void _defaults(ResolvedHttpRouteBuilder b) { + b.method ??= 'POST'; + } + ResolvedHttpRoute._(); + String get method; String get path; Map toJson() { @@ -437,6 +444,7 @@ abstract class ResolvedHttpRoute pb.ResolvedHttpRoute toProto() { return pb.ResolvedHttpRoute( + method: method, path: path, ); } diff --git a/packages/celest_ast/lib/src/resolved_ast.g.dart b/packages/celest_ast/lib/src/resolved_ast.g.dart index 921001e9..a4a79b7a 100644 --- a/packages/celest_ast/lib/src/resolved_ast.g.dart +++ b/packages/celest_ast/lib/src/resolved_ast.g.dart @@ -342,12 +342,13 @@ class _$ResolvedHttpConfigSerializer Serializers serializers, ResolvedHttpConfig object, {FullType specifiedType = FullType.unspecified}) { final result = [ - 'method', - serializers.serialize(object.method, - specifiedType: const FullType(String)), 'route', serializers.serialize(object.route, specifiedType: const FullType(ResolvedHttpRoute)), + 'additionalRoutes', + serializers.serialize(object.additionalRoutes, + specifiedType: const FullType( + BuiltList, const [const FullType(ResolvedHttpRoute)])), 'status', serializers.serialize(object.status, specifiedType: const FullType(int)), 'statusMappings', @@ -371,15 +372,17 @@ class _$ResolvedHttpConfigSerializer iterator.moveNext(); final Object? value = iterator.current; switch (key) { - case 'method': - result.method = serializers.deserialize(value, - specifiedType: const FullType(String))! as String; - break; case 'route': result.route.replace(serializers.deserialize(value, specifiedType: const FullType(ResolvedHttpRoute))! as ResolvedHttpRoute); break; + case 'additionalRoutes': + result.additionalRoutes.replace(serializers.deserialize(value, + specifiedType: const FullType( + BuiltList, const [const FullType(ResolvedHttpRoute)]))! + as BuiltList); + break; case 'status': result.status = serializers.deserialize(value, specifiedType: const FullType(int))! as int; @@ -458,6 +461,9 @@ class _$ResolvedHttpRouteSerializer Iterable serialize(Serializers serializers, ResolvedHttpRoute object, {FullType specifiedType = FullType.unspecified}) { final result = [ + 'method', + serializers.serialize(object.method, + specifiedType: const FullType(String)), 'path', serializers.serialize(object.path, specifiedType: const FullType(String)), ]; @@ -477,6 +483,10 @@ class _$ResolvedHttpRouteSerializer iterator.moveNext(); final Object? value = iterator.current; switch (key) { + case 'method': + result.method = serializers.deserialize(value, + specifiedType: const FullType(String))! as String; + break; case 'path': result.path = serializers.deserialize(value, specifiedType: const FullType(String))! as String; @@ -1834,11 +1844,11 @@ class ResolvedCloudFunctionBuilder } class _$ResolvedHttpConfig extends ResolvedHttpConfig { - @override - final String method; @override final ResolvedHttpRoute route; @override + final BuiltList additionalRoutes; + @override final int status; @override final BuiltMap statusMappings; @@ -1848,15 +1858,15 @@ class _$ResolvedHttpConfig extends ResolvedHttpConfig { (new ResolvedHttpConfigBuilder()..update(updates))._build(); _$ResolvedHttpConfig._( - {required this.method, - required this.route, + {required this.route, + required this.additionalRoutes, required this.status, required this.statusMappings}) : super._() { - BuiltValueNullFieldError.checkNotNull( - method, r'ResolvedHttpConfig', 'method'); BuiltValueNullFieldError.checkNotNull( route, r'ResolvedHttpConfig', 'route'); + BuiltValueNullFieldError.checkNotNull( + additionalRoutes, r'ResolvedHttpConfig', 'additionalRoutes'); BuiltValueNullFieldError.checkNotNull( status, r'ResolvedHttpConfig', 'status'); BuiltValueNullFieldError.checkNotNull( @@ -1876,8 +1886,8 @@ class _$ResolvedHttpConfig extends ResolvedHttpConfig { bool operator ==(Object other) { if (identical(other, this)) return true; return other is ResolvedHttpConfig && - method == other.method && route == other.route && + additionalRoutes == other.additionalRoutes && status == other.status && statusMappings == other.statusMappings; } @@ -1885,8 +1895,8 @@ class _$ResolvedHttpConfig extends ResolvedHttpConfig { @override int get hashCode { var _$hash = 0; - _$hash = $jc(_$hash, method.hashCode); _$hash = $jc(_$hash, route.hashCode); + _$hash = $jc(_$hash, additionalRoutes.hashCode); _$hash = $jc(_$hash, status.hashCode); _$hash = $jc(_$hash, statusMappings.hashCode); _$hash = $jf(_$hash); @@ -1896,8 +1906,8 @@ class _$ResolvedHttpConfig extends ResolvedHttpConfig { @override String toString() { return (newBuiltValueToStringHelper(r'ResolvedHttpConfig') - ..add('method', method) ..add('route', route) + ..add('additionalRoutes', additionalRoutes) ..add('status', status) ..add('statusMappings', statusMappings)) .toString(); @@ -1908,15 +1918,17 @@ class ResolvedHttpConfigBuilder implements Builder { _$ResolvedHttpConfig? _$v; - String? _method; - String? get method => _$this._method; - set method(String? method) => _$this._method = method; - ResolvedHttpRouteBuilder? _route; ResolvedHttpRouteBuilder get route => _$this._route ??= new ResolvedHttpRouteBuilder(); set route(ResolvedHttpRouteBuilder? route) => _$this._route = route; + ListBuilder? _additionalRoutes; + ListBuilder get additionalRoutes => + _$this._additionalRoutes ??= new ListBuilder(); + set additionalRoutes(ListBuilder? additionalRoutes) => + _$this._additionalRoutes = additionalRoutes; + int? _status; int? get status => _$this._status; set status(int? status) => _$this._status = status; @@ -1932,8 +1944,8 @@ class ResolvedHttpConfigBuilder ResolvedHttpConfigBuilder get _$this { final $v = _$v; if ($v != null) { - _method = $v.method; _route = $v.route.toBuilder(); + _additionalRoutes = $v.additionalRoutes.toBuilder(); _status = $v.status; _statusMappings = $v.statusMappings.toBuilder(); _$v = null; @@ -1961,9 +1973,8 @@ class ResolvedHttpConfigBuilder try { _$result = _$v ?? new _$ResolvedHttpConfig._( - method: BuiltValueNullFieldError.checkNotNull( - method, r'ResolvedHttpConfig', 'method'), route: route.build(), + additionalRoutes: additionalRoutes.build(), status: BuiltValueNullFieldError.checkNotNull( status, r'ResolvedHttpConfig', 'status'), statusMappings: statusMappings.build()); @@ -1972,6 +1983,8 @@ class ResolvedHttpConfigBuilder try { _$failedField = 'route'; route.build(); + _$failedField = 'additionalRoutes'; + additionalRoutes.build(); _$failedField = 'statusMappings'; statusMappings.build(); @@ -2068,6 +2081,8 @@ class ResolvedStreamConfigBuilder } class _$ResolvedHttpRoute extends ResolvedHttpRoute { + @override + final String method; @override final String path; @@ -2075,7 +2090,10 @@ class _$ResolvedHttpRoute extends ResolvedHttpRoute { [void Function(ResolvedHttpRouteBuilder)? updates]) => (new ResolvedHttpRouteBuilder()..update(updates))._build(); - _$ResolvedHttpRoute._({required this.path}) : super._() { + _$ResolvedHttpRoute._({required this.method, required this.path}) + : super._() { + BuiltValueNullFieldError.checkNotNull( + method, r'ResolvedHttpRoute', 'method'); BuiltValueNullFieldError.checkNotNull(path, r'ResolvedHttpRoute', 'path'); } @@ -2090,12 +2108,15 @@ class _$ResolvedHttpRoute extends ResolvedHttpRoute { @override bool operator ==(Object other) { if (identical(other, this)) return true; - return other is ResolvedHttpRoute && path == other.path; + return other is ResolvedHttpRoute && + method == other.method && + path == other.path; } @override int get hashCode { var _$hash = 0; + _$hash = $jc(_$hash, method.hashCode); _$hash = $jc(_$hash, path.hashCode); _$hash = $jf(_$hash); return _$hash; @@ -2104,6 +2125,7 @@ class _$ResolvedHttpRoute extends ResolvedHttpRoute { @override String toString() { return (newBuiltValueToStringHelper(r'ResolvedHttpRoute') + ..add('method', method) ..add('path', path)) .toString(); } @@ -2113,6 +2135,10 @@ class ResolvedHttpRouteBuilder implements Builder { _$ResolvedHttpRoute? _$v; + String? _method; + String? get method => _$this._method; + set method(String? method) => _$this._method = method; + String? _path; String? get path => _$this._path; set path(String? path) => _$this._path = path; @@ -2122,6 +2148,7 @@ class ResolvedHttpRouteBuilder ResolvedHttpRouteBuilder get _$this { final $v = _$v; if ($v != null) { + _method = $v.method; _path = $v.path; _$v = null; } @@ -2143,8 +2170,11 @@ class ResolvedHttpRouteBuilder ResolvedHttpRoute build() => _build(); _$ResolvedHttpRoute _build() { + ResolvedHttpRoute._defaults(this); final _$result = _$v ?? new _$ResolvedHttpRoute._( + method: BuiltValueNullFieldError.checkNotNull( + method, r'ResolvedHttpRoute', 'method'), path: BuiltValueNullFieldError.checkNotNull( path, r'ResolvedHttpRoute', 'path')); replace(_$result); diff --git a/packages/celest_ast/lib/src/serializers.g.dart b/packages/celest_ast/lib/src/serializers.g.dart index 93e56fb0..48636554 100644 --- a/packages/celest_ast/lib/src/serializers.g.dart +++ b/packages/celest_ast/lib/src/serializers.g.dart @@ -128,6 +128,13 @@ Serializers _$serializers = (new Serializers().toBuilder() const FullType( BuiltList, const [const FullType(ResolvedExternalAuthProvider)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(ResolvedHttpRoute)]), + () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltMap, + const [const FullType(TypeReference), const FullType(int)]), + () => new MapBuilder()) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder()) @@ -194,10 +201,6 @@ Serializers _$serializers = (new Serializers().toBuilder() const FullType(ResolvedCloudFunction) ]), () => new MapBuilder()) - ..addBuilderFactory( - const FullType(BuiltMap, - const [const FullType(TypeReference), const FullType(int)]), - () => new MapBuilder()) ..addBuilderFactory( const FullType(BuiltSet, const [const FullType(FeatureFlag)]), () => new SetBuilder()) diff --git a/packages/celest_ast/proto/celest/ast/v1/resolved_ast.proto b/packages/celest_ast/proto/celest/ast/v1/resolved_ast.proto index 30175ec7..a5a1f585 100644 --- a/packages/celest_ast/proto/celest/ast/v1/resolved_ast.proto +++ b/packages/celest_ast/proto/celest/ast/v1/resolved_ast.proto @@ -81,14 +81,14 @@ message ResolvedFunction { // The HTTP configuration of a [ResolvedFunction][]. message ResolvedHttpConfig { - // The HTTP method of the function. - string method = 1 [(google.api.field_behavior) = REQUIRED]; + // The successful status code of the function. + int32 status = 1 [(google.api.field_behavior) = REQUIRED]; // The resolved route configuration of the function. ResolvedHttpRoute route = 2 [(google.api.field_behavior) = REQUIRED]; - // The successful status code of the function. - int32 status = 3 [(google.api.field_behavior) = REQUIRED]; + // Additional route configurations of the function. + repeated ResolvedHttpRoute additional_routes = 3 [(google.api.field_behavior) = OPTIONAL]; // A mapping of Dart types to HTTP status codes. // @@ -104,8 +104,11 @@ message ResolvedHttpConfig { // A route to an HTTP endpoint. message ResolvedHttpRoute { - // The path of the HTTP endpoint. - string path = 1 [(google.api.field_behavior) = REQUIRED]; + // The HTTP method of the route. + string method = 1 [(google.api.field_behavior) = REQUIRED]; + + // The path to the HTTP endpoint. + string path = 2 [(google.api.field_behavior) = REQUIRED]; } // The stream configuration of a [ResolvedFunction][]. diff --git a/packages/celest_ast/test/celest_ast_test.dart b/packages/celest_ast/test/celest_ast_test.dart index 20451db0..28a2e4e6 100644 --- a/packages/celest_ast/test/celest_ast_test.dart +++ b/packages/celest_ast/test/celest_ast_test.dart @@ -63,8 +63,8 @@ void main() { type: StreamType.unidirectionalClient, ), httpConfig: ResolvedHttpConfig( - method: 'PATCH', route: ResolvedHttpRoute( + method: 'PATCH', path: '/greeting/hello-world', ), status: 204, diff --git a/packages/celest_auth/lib/src/model/cloud_interop.dart b/packages/celest_auth/lib/src/model/cloud_interop.dart index d77cd661..f8a962ec 100644 --- a/packages/celest_auth/lib/src/model/cloud_interop.dart +++ b/packages/celest_auth/lib/src/model/cloud_interop.dart @@ -8,12 +8,29 @@ import 'package:meta/meta.dart'; extension ToCelestUser on cloud.User { User toCelest() => User( userId: userId, - email: hasEmail() ? email.email : null, - emailVerified: hasEmail() ? email.verified : null, - displayName: hasGivenName() - ? hasFamilyName() - ? '$givenName $familyName' - : givenName - : null, + createTime: createTime.toDateTime(), + updateTime: hasUpdateTime() ? updateTime.toDateTime() : null, + givenName: hasGivenName() ? givenName : null, + familyName: hasFamilyName() ? familyName : null, + timeZone: hasTimeZone() ? timeZone : null, + languageCode: hasLanguageCode() ? languageCode : null, + emails: emails.map((e) => e.toCelest()).toList(), + phoneNumbers: phoneNumbers.map((pn) => pn.toCelest()).toList(), + ); +} + +extension ToCelestEmail on cloud.Email { + Email toCelest() => Email( + email: email, + isVerified: isVerified, + isPrimary: isPrimary, + ); +} + +extension ToCelestPhoneNumber on cloud.PhoneNumber { + PhoneNumber toCelest() => PhoneNumber( + phoneNumber: phoneNumber, + isVerified: isVerified, + isPrimary: isPrimary, ); } diff --git a/packages/celest_cloud/lib/src/cloud/cloud.dart b/packages/celest_cloud/lib/src/cloud/cloud.dart index 493e4b32..afc67270 100644 --- a/packages/celest_cloud/lib/src/cloud/cloud.dart +++ b/packages/celest_cloud/lib/src/cloud/cloud.dart @@ -35,6 +35,9 @@ class CelestCloud { authenticator: authenticator, logger: logger, ), + _baseUri = uri, + _httpClient = httpClient, + _authenticator = authenticator, _clientType = clientType ?? _defaultClientType, _logger = logger ?? Logger('Celest.Cloud'); @@ -60,12 +63,23 @@ class CelestCloud { ProjectEnvironment(), ]); + final Uri _baseUri; + final http.Client? _httpClient; + final Authenticator _authenticator; + final CloudProtocol _protocol; final ClientType _clientType; final Logger _logger; + late final CloudProtocolHttp _httpProtocol = CloudProtocolHttp( + uri: _baseUri, + authenticator: _authenticator, + httpClient: _httpClient, + logger: _logger, + ); + late final Authentication authentication = Authentication( - protocol: _protocol.authentication, + protocol: _httpProtocol.authentication, clientType: _clientType, logger: _logger, ); @@ -77,7 +91,7 @@ class CelestCloud { ); late final Users users = Users( - protocol: _protocol.users, + protocol: _httpProtocol.users, logger: _logger, ); diff --git a/packages/celest_cloud/lib/src/cloud/users/users_protocol.http.dart b/packages/celest_cloud/lib/src/cloud/users/users_protocol.http.dart index e5db5416..b27f648c 100644 --- a/packages/celest_cloud/lib/src/cloud/users/users_protocol.http.dart +++ b/packages/celest_cloud/lib/src/cloud/users/users_protocol.http.dart @@ -18,7 +18,7 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { @override Future get(GetUserRequest request) async { - final path = '/v1alpha1/${request.name}'; + final path = '/v1alpha1/auth/${request.name}'; final url = _baseUri.replace(path: path); final req = http.Request('GET', url) ..headers['content-type'] = 'application/json' @@ -40,7 +40,7 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { @override Future getMembership(GetUserMembershipRequest request) async { - final path = '/v1alpha1/${request.name}'; + final path = '/v1alpha1/auth/${request.name}'; final url = _baseUri.replace(path: path); final req = http.Request('GET', url) ..headers['content-type'] = 'application/json' @@ -62,12 +62,8 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { @override Future list(ListUsersRequest request) async { - final path = switch (request.parent) { - '' => '/v1alpha1/organizations/celest-dev/users', - final parent => '/v1alpha1/$parent/users', - }; final url = _baseUri.replace( - path: path, + path: '/v1alpha1/auth/users', queryParameters: { if (request.hasPageSize()) 'pageSize': request.pageSize.toString(), if (request.hasPageToken()) 'pageToken': request.pageToken, @@ -97,7 +93,7 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { Future listMemberships( ListUserMembershipsRequest request, ) async { - final path = '/v1alpha1/${request.parent}/memberships'; + final path = '/v1alpha1/auth/${request.parent}/memberships'; final url = _baseUri.replace( path: path, queryParameters: { @@ -126,7 +122,7 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { @override Future update(UpdateUserRequest request) async { - final path = '/v1alpha1/${request.user.name}'; + final path = '/v1alpha1/auth/${request.user.name}'; final url = _baseUri.replace( path: path, queryParameters: { @@ -161,7 +157,7 @@ final class UsersProtocolHttp with BaseProtocol implements UsersProtocol { @override Future delete(DeleteUserRequest request) async { - final path = '/v1alpha1/${request.name}'; + final path = '/v1alpha1/auth/${request.name}'; final url = _baseUri.replace( path: path, queryParameters: { diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart index a31795a9..2c15f2ca 100644 --- a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pb.dart @@ -13,45 +13,51 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -import '../../../cedar/v3/policy.pb.dart' as $53; +import '../../../cedar/v3/policy.pb.dart' as $52; import '../../../google/protobuf/struct.pb.dart' as $22; import 'features.pbenum.dart' as $54; import 'resolved_ast.pbenum.dart'; +import 'sdks.pb.dart' as $53; +import 'sdks.pbenum.dart' as $53; export 'resolved_ast.pbenum.dart'; /// The resolved AST of a Celest project. class ResolvedProject extends $pb.GeneratedMessage { factory ResolvedProject({ - $core.String? name, + $core.String? projectId, + $core.String? environmentId, $core.Map<$core.String, ResolvedApi>? apis, - $core.Iterable? environmentVariables, - ResolvedAuth? auth, - SdkInfo? sdk, - $core.Iterable<$54.FeatureFlag>? featureFlags, + $core.Iterable? variables, $core.Iterable? secrets, + ResolvedAuth? auth, + $core.Map<$core.String, ResolvedDatabase>? databases, + SdkConfiguration? sdkConfig, }) { final $result = create(); - if (name != null) { - $result.name = name; + if (projectId != null) { + $result.projectId = projectId; + } + if (environmentId != null) { + $result.environmentId = environmentId; } if (apis != null) { $result.apis.addAll(apis); } - if (environmentVariables != null) { - $result.environmentVariables.addAll(environmentVariables); + if (variables != null) { + $result.variables.addAll(variables); + } + if (secrets != null) { + $result.secrets.addAll(secrets); } if (auth != null) { $result.auth = auth; } - if (sdk != null) { - $result.sdk = sdk; + if (databases != null) { + $result.databases.addAll(databases); } - if (featureFlags != null) { - $result.featureFlags.addAll(featureFlags); - } - if (secrets != null) { - $result.secrets.addAll(secrets); + if (sdkConfig != null) { + $result.sdkConfig = sdkConfig; } return $result; } @@ -67,28 +73,32 @@ class ResolvedProject extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedProject', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'name') - ..m<$core.String, ResolvedApi>(2, _omitFieldNames ? '' : 'apis', + ..aOS(1, _omitFieldNames ? '' : 'projectId') + ..aOS(2, _omitFieldNames ? '' : 'environmentId') + ..m<$core.String, ResolvedApi>(3, _omitFieldNames ? '' : 'apis', entryClassName: 'ResolvedProject.ApisEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: ResolvedApi.create, valueDefaultOrMaker: ResolvedApi.getDefault, packageName: const $pb.PackageName('celest.ast.v1')) - ..pc( - 3, _omitFieldNames ? '' : 'environmentVariables', $pb.PbFieldType.PM, - subBuilder: ResolvedEnvironmentVariable.create) - ..aOM(4, _omitFieldNames ? '' : 'auth', - subBuilder: ResolvedAuth.create) - ..aOM(5, _omitFieldNames ? '' : 'sdk', subBuilder: SdkInfo.create) - ..pc<$54.FeatureFlag>( - 6, _omitFieldNames ? '' : 'featureFlags', $pb.PbFieldType.KE, - valueOf: $54.FeatureFlag.valueOf, - enumValues: $54.FeatureFlag.values, - defaultEnumValue: $54.FeatureFlag.FEATURE_FLAG_UNSPECIFIED) + ..pc( + 4, _omitFieldNames ? '' : 'variables', $pb.PbFieldType.PM, + subBuilder: ResolvedVariable.create) ..pc( - 7, _omitFieldNames ? '' : 'secrets', $pb.PbFieldType.PM, + 5, _omitFieldNames ? '' : 'secrets', $pb.PbFieldType.PM, subBuilder: ResolvedSecret.create) + ..aOM(6, _omitFieldNames ? '' : 'auth', + subBuilder: ResolvedAuth.create) + ..m<$core.String, ResolvedDatabase>(7, _omitFieldNames ? '' : 'databases', + entryClassName: 'ResolvedProject.DatabasesEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OM, + valueCreator: ResolvedDatabase.create, + valueDefaultOrMaker: ResolvedDatabase.getDefault, + packageName: const $pb.PackageName('celest.ast.v1')) + ..aOM(99, _omitFieldNames ? '' : 'sdkConfig', + subBuilder: SdkConfiguration.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -114,65 +124,77 @@ class ResolvedProject extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(create); static ResolvedProject? _defaultInstance; - /// The user-provided name of the project. + /// The user-provided identifier of the project. @$pb.TagNumber(1) - $core.String get name => $_getSZ(0); + $core.String get projectId => $_getSZ(0); @$pb.TagNumber(1) - set name($core.String v) { + set projectId($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasName() => $_has(0); + $core.bool hasProjectId() => $_has(0); @$pb.TagNumber(1) - void clearName() => clearField(1); + void clearProjectId() => clearField(1); - /// The project's API endpoints, keyed by their `name`. + /// The ID of the environment this projects resolves to. + @$pb.TagNumber(2) + $core.String get environmentId => $_getSZ(1); @$pb.TagNumber(2) - $core.Map<$core.String, ResolvedApi> get apis => $_getMap(1); + set environmentId($core.String v) { + $_setString(1, v); + } - /// The project's environment variables. - @$pb.TagNumber(3) - $core.List get environmentVariables => - $_getList(2); + @$pb.TagNumber(2) + $core.bool hasEnvironmentId() => $_has(1); + @$pb.TagNumber(2) + void clearEnvironmentId() => clearField(2); - /// The project's auth configuration. - @$pb.TagNumber(4) - ResolvedAuth get auth => $_getN(3); - @$pb.TagNumber(4) - set auth(ResolvedAuth v) { - setField(4, v); - } + /// The project's API endpoints, keyed by their `name`. + @$pb.TagNumber(3) + $core.Map<$core.String, ResolvedApi> get apis => $_getMap(2); + /// The project's environment variables. @$pb.TagNumber(4) - $core.bool hasAuth() => $_has(3); - @$pb.TagNumber(4) - void clearAuth() => clearField(4); - @$pb.TagNumber(4) - ResolvedAuth ensureAuth() => $_ensure(3); + $core.List get variables => $_getList(3); - /// The Dart or Flutter SDK used to deploy the project. - @$pb.TagNumber(5) - SdkInfo get sdk => $_getN(4); + /// The project's secrets. @$pb.TagNumber(5) - set sdk(SdkInfo v) { - setField(5, v); - } + $core.List get secrets => $_getList(4); - @$pb.TagNumber(5) - $core.bool hasSdk() => $_has(4); - @$pb.TagNumber(5) - void clearSdk() => clearField(5); - @$pb.TagNumber(5) - SdkInfo ensureSdk() => $_ensure(4); + /// The project's auth configuration. + @$pb.TagNumber(6) + ResolvedAuth get auth => $_getN(5); + @$pb.TagNumber(6) + set auth(ResolvedAuth v) { + setField(6, v); + } - /// The feature flags enabled by the project. @$pb.TagNumber(6) - $core.List<$54.FeatureFlag> get featureFlags => $_getList(5); + $core.bool hasAuth() => $_has(5); + @$pb.TagNumber(6) + void clearAuth() => clearField(6); + @$pb.TagNumber(6) + ResolvedAuth ensureAuth() => $_ensure(5); - /// The project's secrets. + /// The project's databases. @$pb.TagNumber(7) - $core.List get secrets => $_getList(6); + $core.Map<$core.String, ResolvedDatabase> get databases => $_getMap(6); + + /// Configuration of the Dart, Flutter, and Celest SDKs used to deploy the project. + @$pb.TagNumber(99) + SdkConfiguration get sdkConfig => $_getN(7); + @$pb.TagNumber(99) + set sdkConfig(SdkConfiguration v) { + setField(99, v); + } + + @$pb.TagNumber(99) + $core.bool hasSdkConfig() => $_has(7); + @$pb.TagNumber(99) + void clearSdkConfig() => clearField(99); + @$pb.TagNumber(99) + SdkConfiguration ensureSdkConfig() => $_ensure(7); } /// The resolved AST of a Celest API. @@ -180,7 +202,7 @@ class ResolvedApi extends $pb.GeneratedMessage { factory ResolvedApi({ $core.String? apiId, $core.Map<$core.String, ResolvedFunction>? functions, - $53.PolicySet? policySet, + $52.PolicySet? policySet, }) { final $result = create(); if (apiId != null) { @@ -214,8 +236,8 @@ class ResolvedApi extends $pb.GeneratedMessage { valueCreator: ResolvedFunction.create, valueDefaultOrMaker: ResolvedFunction.getDefault, packageName: const $pb.PackageName('celest.ast.v1')) - ..aOM<$53.PolicySet>(3, _omitFieldNames ? '' : 'policySet', - subBuilder: $53.PolicySet.create) + ..aOM<$52.PolicySet>(3, _omitFieldNames ? '' : 'policySet', + subBuilder: $52.PolicySet.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -264,9 +286,9 @@ class ResolvedApi extends $pb.GeneratedMessage { /// /// This policy set is applied to all functions within the API. @$pb.TagNumber(3) - $53.PolicySet get policySet => $_getN(2); + $52.PolicySet get policySet => $_getN(2); @$pb.TagNumber(3) - set policySet($53.PolicySet v) { + set policySet($52.PolicySet v) { setField(3, v); } @@ -275,39 +297,35 @@ class ResolvedApi extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearPolicySet() => clearField(3); @$pb.TagNumber(3) - $53.PolicySet ensurePolicySet() => $_ensure(2); + $52.PolicySet ensurePolicySet() => $_ensure(2); } /// The resolved AST of a Celest function. class ResolvedFunction extends $pb.GeneratedMessage { factory ResolvedFunction({ $core.String? functionId, - $core.String? apiId, - ResolvedHttpConfig? http, - $core.bool? clientStreaming, - $core.bool? serverStreaming, - $core.Iterable<$core.String>? environmentVariables, + $core.String? parentId, + ResolvedHttpConfig? httpConfig, + ResolvedStreamConfig? streamConfig, + $core.Iterable<$core.String>? variables, $core.Iterable<$core.String>? secrets, - $53.PolicySet? policySet, + $52.PolicySet? policySet, }) { final $result = create(); if (functionId != null) { $result.functionId = functionId; } - if (apiId != null) { - $result.apiId = apiId; - } - if (http != null) { - $result.http = http; + if (parentId != null) { + $result.parentId = parentId; } - if (clientStreaming != null) { - $result.clientStreaming = clientStreaming; + if (httpConfig != null) { + $result.httpConfig = httpConfig; } - if (serverStreaming != null) { - $result.serverStreaming = serverStreaming; + if (streamConfig != null) { + $result.streamConfig = streamConfig; } - if (environmentVariables != null) { - $result.environmentVariables.addAll(environmentVariables); + if (variables != null) { + $result.variables.addAll(variables); } if (secrets != null) { $result.secrets.addAll(secrets); @@ -330,15 +348,15 @@ class ResolvedFunction extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'functionId') - ..aOS(2, _omitFieldNames ? '' : 'apiId') - ..aOM(3, _omitFieldNames ? '' : 'http', + ..aOS(2, _omitFieldNames ? '' : 'parentId') + ..aOM(3, _omitFieldNames ? '' : 'httpConfig', subBuilder: ResolvedHttpConfig.create) - ..aOB(4, _omitFieldNames ? '' : 'clientStreaming') - ..aOB(5, _omitFieldNames ? '' : 'serverStreaming') - ..pPS(6, _omitFieldNames ? '' : 'environmentVariables') - ..pPS(7, _omitFieldNames ? '' : 'secrets') - ..aOM<$53.PolicySet>(8, _omitFieldNames ? '' : 'policySet', - subBuilder: $53.PolicySet.create) + ..aOM(4, _omitFieldNames ? '' : 'streamConfig', + subBuilder: ResolvedStreamConfig.create) + ..pPS(5, _omitFieldNames ? '' : 'variables') + ..pPS(6, _omitFieldNames ? '' : 'secrets') + ..aOM<$52.PolicySet>(7, _omitFieldNames ? '' : 'policySet', + subBuilder: $52.PolicySet.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -382,102 +400,91 @@ class ResolvedFunction extends $pb.GeneratedMessage { /// The ID of the function's parent API. @$pb.TagNumber(2) - $core.String get apiId => $_getSZ(1); + $core.String get parentId => $_getSZ(1); @$pb.TagNumber(2) - set apiId($core.String v) { + set parentId($core.String v) { $_setString(1, v); } @$pb.TagNumber(2) - $core.bool hasApiId() => $_has(1); + $core.bool hasParentId() => $_has(1); @$pb.TagNumber(2) - void clearApiId() => clearField(2); + void clearParentId() => clearField(2); /// The resolved HTTP configuration of the function. @$pb.TagNumber(3) - ResolvedHttpConfig get http => $_getN(2); + ResolvedHttpConfig get httpConfig => $_getN(2); @$pb.TagNumber(3) - set http(ResolvedHttpConfig v) { + set httpConfig(ResolvedHttpConfig v) { setField(3, v); } @$pb.TagNumber(3) - $core.bool hasHttp() => $_has(2); + $core.bool hasHttpConfig() => $_has(2); @$pb.TagNumber(3) - void clearHttp() => clearField(3); + void clearHttpConfig() => clearField(3); @$pb.TagNumber(3) - ResolvedHttpConfig ensureHttp() => $_ensure(2); + ResolvedHttpConfig ensureHttpConfig() => $_ensure(2); - /// Whether the function is a client-streaming RPC. + /// The resolved stream configuration of the function. @$pb.TagNumber(4) - $core.bool get clientStreaming => $_getBF(3); + ResolvedStreamConfig get streamConfig => $_getN(3); @$pb.TagNumber(4) - set clientStreaming($core.bool v) { - $_setBool(3, v); + set streamConfig(ResolvedStreamConfig v) { + setField(4, v); } @$pb.TagNumber(4) - $core.bool hasClientStreaming() => $_has(3); + $core.bool hasStreamConfig() => $_has(3); @$pb.TagNumber(4) - void clearClientStreaming() => clearField(4); - - /// Whether the function is a server-streaming RPC. - @$pb.TagNumber(5) - $core.bool get serverStreaming => $_getBF(4); - @$pb.TagNumber(5) - set serverStreaming($core.bool v) { - $_setBool(4, v); - } + void clearStreamConfig() => clearField(4); + @$pb.TagNumber(4) + ResolvedStreamConfig ensureStreamConfig() => $_ensure(3); + /// The variables required by the function. @$pb.TagNumber(5) - $core.bool hasServerStreaming() => $_has(4); - @$pb.TagNumber(5) - void clearServerStreaming() => clearField(5); + $core.List<$core.String> get variables => $_getList(4); - /// The environment variables required by the function. + /// The secrets required by the function. @$pb.TagNumber(6) - $core.List<$core.String> get environmentVariables => $_getList(5); + $core.List<$core.String> get secrets => $_getList(5); - /// The secrets required by the function. + /// The policy set declared by the function. @$pb.TagNumber(7) - $core.List<$core.String> get secrets => $_getList(6); + $52.PolicySet get policySet => $_getN(6); + @$pb.TagNumber(7) + set policySet($52.PolicySet v) { + setField(7, v); + } - /// The policy set declared by the function. - @$pb.TagNumber(8) - $53.PolicySet get policySet => $_getN(7); - @$pb.TagNumber(8) - set policySet($53.PolicySet v) { - setField(8, v); - } - - @$pb.TagNumber(8) - $core.bool hasPolicySet() => $_has(7); - @$pb.TagNumber(8) - void clearPolicySet() => clearField(8); - @$pb.TagNumber(8) - $53.PolicySet ensurePolicySet() => $_ensure(7); + @$pb.TagNumber(7) + $core.bool hasPolicySet() => $_has(6); + @$pb.TagNumber(7) + void clearPolicySet() => clearField(7); + @$pb.TagNumber(7) + $52.PolicySet ensurePolicySet() => $_ensure(6); } /// The HTTP configuration of a [ResolvedFunction][]. class ResolvedHttpConfig extends $pb.GeneratedMessage { factory ResolvedHttpConfig({ - $core.String? method, - HttpPath? path, - $core.int? statusCode, - $core.Map<$core.String, $core.int>? errorStatus, + $core.int? status, + ResolvedHttpRoute? route, + $core.Iterable? additionalRoutes, + $core.Map<$core.String, $core.int>? statusMappings, }) { final $result = create(); - if (method != null) { - $result.method = method; + if (status != null) { + $result.status = status; } - if (path != null) { - $result.path = path; + if (route != null) { + $result.route = route; } - if (statusCode != null) { - $result.statusCode = statusCode; + if (additionalRoutes != null) { + $result.additionalRoutes.addAll(additionalRoutes); } - if (errorStatus != null) { - $result.errorStatus.addAll(errorStatus); + if (statusMappings != null) { + $result.statusMappings.addAll(statusMappings); } return $result; } @@ -493,12 +500,14 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedHttpConfig', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'method') - ..aOM(2, _omitFieldNames ? '' : 'path', - subBuilder: HttpPath.create) - ..a<$core.int>(3, _omitFieldNames ? '' : 'statusCode', $pb.PbFieldType.O3) - ..m<$core.String, $core.int>(4, _omitFieldNames ? '' : 'errorStatus', - entryClassName: 'ResolvedHttpConfig.ErrorStatusEntry', + ..a<$core.int>(1, _omitFieldNames ? '' : 'status', $pb.PbFieldType.O3) + ..aOM(2, _omitFieldNames ? '' : 'route', + subBuilder: ResolvedHttpRoute.create) + ..pc( + 3, _omitFieldNames ? '' : 'additionalRoutes', $pb.PbFieldType.PM, + subBuilder: ResolvedHttpRoute.create) + ..m<$core.String, $core.int>(4, _omitFieldNames ? '' : 'statusMappings', + entryClassName: 'ResolvedHttpConfig.StatusMappingsEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.O3, packageName: const $pb.PackageName('celest.ast.v1')) @@ -527,51 +536,41 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(create); static ResolvedHttpConfig? _defaultInstance; - /// The HTTP method of the function. + /// The successful status code of the function. @$pb.TagNumber(1) - $core.String get method => $_getSZ(0); + $core.int get status => $_getIZ(0); @$pb.TagNumber(1) - set method($core.String v) { - $_setString(0, v); + set status($core.int v) { + $_setSignedInt32(0, v); } @$pb.TagNumber(1) - $core.bool hasMethod() => $_has(0); + $core.bool hasStatus() => $_has(0); @$pb.TagNumber(1) - void clearMethod() => clearField(1); + void clearStatus() => clearField(1); - /// The path of the function. + /// The resolved route configuration of the function. @$pb.TagNumber(2) - HttpPath get path => $_getN(1); + ResolvedHttpRoute get route => $_getN(1); @$pb.TagNumber(2) - set path(HttpPath v) { + set route(ResolvedHttpRoute v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasPath() => $_has(1); + $core.bool hasRoute() => $_has(1); @$pb.TagNumber(2) - void clearPath() => clearField(2); + void clearRoute() => clearField(2); @$pb.TagNumber(2) - HttpPath ensurePath() => $_ensure(1); + ResolvedHttpRoute ensureRoute() => $_ensure(1); - /// The successful status code of the function. - @$pb.TagNumber(3) - $core.int get statusCode => $_getIZ(2); - @$pb.TagNumber(3) - set statusCode($core.int v) { - $_setSignedInt32(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasStatusCode() => $_has(2); + /// Additional route configurations of the function. @$pb.TagNumber(3) - void clearStatusCode() => clearField(3); + $core.List get additionalRoutes => $_getList(2); - /// The error status mapping of the function. + /// A mapping of Dart types to HTTP status codes. /// - /// The keys are the Dart types of the errors, and the values are the HTTP status codes. - /// Dart types are represented as URLs, with a scheme, host and path identifying the location + /// Dart types are represented as URIs with a scheme, host and path identifying the location /// of the type's definition, and a fragment identifying the type itself. /// /// For example: @@ -579,195 +578,162 @@ class ResolvedHttpConfig extends $pb.GeneratedMessage { /// - `dart:core#String` /// - `package:celest/functions/greeting.dart#GreetingError` @$pb.TagNumber(4) - $core.Map<$core.String, $core.int> get errorStatus => $_getMap(3); + $core.Map<$core.String, $core.int> get statusMappings => $_getMap(3); } -/// The path to an HTTP endpoint and its parameters. -class HttpPath extends $pb.GeneratedMessage { - factory HttpPath({ +/// A route to an HTTP endpoint. +class ResolvedHttpRoute extends $pb.GeneratedMessage { + factory ResolvedHttpRoute({ + $core.String? method, $core.String? path, - $core.Map<$core.String, HttpParameter>? parameters, }) { final $result = create(); + if (method != null) { + $result.method = method; + } if (path != null) { $result.path = path; } - if (parameters != null) { - $result.parameters.addAll(parameters); - } return $result; } - HttpPath._() : super(); - factory HttpPath.fromBuffer($core.List<$core.int> i, + ResolvedHttpRoute._() : super(); + factory ResolvedHttpRoute.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory HttpPath.fromJson($core.String i, + factory ResolvedHttpRoute.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'HttpPath', + _omitMessageNames ? '' : 'ResolvedHttpRoute', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'path') - ..m<$core.String, HttpParameter>(2, _omitFieldNames ? '' : 'parameters', - entryClassName: 'HttpPath.ParametersEntry', - keyFieldType: $pb.PbFieldType.OS, - valueFieldType: $pb.PbFieldType.OM, - valueCreator: HttpParameter.create, - valueDefaultOrMaker: HttpParameter.getDefault, - packageName: const $pb.PackageName('celest.ast.v1')) + ..aOS(1, _omitFieldNames ? '' : 'method') + ..aOS(2, _omitFieldNames ? '' : 'path') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - HttpPath clone() => HttpPath()..mergeFromMessage(this); + ResolvedHttpRoute clone() => ResolvedHttpRoute()..mergeFromMessage(this); @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - HttpPath copyWith(void Function(HttpPath) updates) => - super.copyWith((message) => updates(message as HttpPath)) as HttpPath; + ResolvedHttpRoute copyWith(void Function(ResolvedHttpRoute) updates) => + super.copyWith((message) => updates(message as ResolvedHttpRoute)) + as ResolvedHttpRoute; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static HttpPath create() => HttpPath._(); - HttpPath createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static ResolvedHttpRoute create() => ResolvedHttpRoute._(); + ResolvedHttpRoute createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); @$core.pragma('dart2js:noInline') - static HttpPath getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static HttpPath? _defaultInstance; + static ResolvedHttpRoute getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedHttpRoute? _defaultInstance; - /// The path of the HTTP endpoint. + /// The HTTP method of the route. @$pb.TagNumber(1) - $core.String get path => $_getSZ(0); + $core.String get method => $_getSZ(0); @$pb.TagNumber(1) - set path($core.String v) { + set method($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasPath() => $_has(0); + $core.bool hasMethod() => $_has(0); @$pb.TagNumber(1) - void clearPath() => clearField(1); + void clearMethod() => clearField(1); + + /// The path to the HTTP endpoint. + @$pb.TagNumber(2) + $core.String get path => $_getSZ(1); + @$pb.TagNumber(2) + set path($core.String v) { + $_setString(1, v); + } - /// The parameters of the HTTP endpoint. @$pb.TagNumber(2) - $core.Map<$core.String, HttpParameter> get parameters => $_getMap(1); + $core.bool hasPath() => $_has(1); + @$pb.TagNumber(2) + void clearPath() => clearField(2); } -/// A parameter of an HTTP endpoint. -class HttpParameter extends $pb.GeneratedMessage { - factory HttpParameter({ - $core.String? name, - $core.String? type, - $core.bool? required, +/// The stream configuration of a [ResolvedFunction][]. +class ResolvedStreamConfig extends $pb.GeneratedMessage { + factory ResolvedStreamConfig({ + ResolvedStreamConfig_Type? type, }) { final $result = create(); - if (name != null) { - $result.name = name; - } if (type != null) { $result.type = type; } - if (required != null) { - $result.required = required; - } return $result; } - HttpParameter._() : super(); - factory HttpParameter.fromBuffer($core.List<$core.int> i, + ResolvedStreamConfig._() : super(); + factory ResolvedStreamConfig.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory HttpParameter.fromJson($core.String i, + factory ResolvedStreamConfig.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'HttpParameter', + _omitMessageNames ? '' : 'ResolvedStreamConfig', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'name') - ..aOS(2, _omitFieldNames ? '' : 'type') - ..aOB(3, _omitFieldNames ? '' : 'required') + ..e( + 1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, + defaultOrMaker: + ResolvedStreamConfig_Type.STREAM_CONFIG_TYPE_UNSPECIFIED, + valueOf: ResolvedStreamConfig_Type.valueOf, + enumValues: ResolvedStreamConfig_Type.values) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - HttpParameter clone() => HttpParameter()..mergeFromMessage(this); + ResolvedStreamConfig clone() => + ResolvedStreamConfig()..mergeFromMessage(this); @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - HttpParameter copyWith(void Function(HttpParameter) updates) => - super.copyWith((message) => updates(message as HttpParameter)) - as HttpParameter; + ResolvedStreamConfig copyWith(void Function(ResolvedStreamConfig) updates) => + super.copyWith((message) => updates(message as ResolvedStreamConfig)) + as ResolvedStreamConfig; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static HttpParameter create() => HttpParameter._(); - HttpParameter createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); + static ResolvedStreamConfig create() => ResolvedStreamConfig._(); + ResolvedStreamConfig createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); @$core.pragma('dart2js:noInline') - static HttpParameter getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static HttpParameter? _defaultInstance; + static ResolvedStreamConfig getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedStreamConfig? _defaultInstance; - /// The name of the parameter. + /// The type of the stream configuration. @$pb.TagNumber(1) - $core.String get name => $_getSZ(0); + ResolvedStreamConfig_Type get type => $_getN(0); @$pb.TagNumber(1) - set name($core.String v) { - $_setString(0, v); + set type(ResolvedStreamConfig_Type v) { + setField(1, v); } @$pb.TagNumber(1) - $core.bool hasName() => $_has(0); + $core.bool hasType() => $_has(0); @$pb.TagNumber(1) - void clearName() => clearField(1); - - /// The Dart type of the parameter. - /// - /// Dart types are represented as URLs, with a scheme, host and path identifying the location - /// of the type's definition, and a fragment identifying the type itself. - /// - /// For example: - /// - /// - `dart:core#String` - /// - `package:celest/functions/greeting.dart#GreetingError` - @$pb.TagNumber(2) - $core.String get type => $_getSZ(1); - @$pb.TagNumber(2) - set type($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasType() => $_has(1); - @$pb.TagNumber(2) - void clearType() => clearField(2); - - /// Whether the parameter is required. - @$pb.TagNumber(3) - $core.bool get required => $_getBF(2); - @$pb.TagNumber(3) - set required($core.bool v) { - $_setBool(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasRequired() => $_has(2); - @$pb.TagNumber(3) - void clearRequired() => clearField(3); + void clearType() => clearField(1); } /// An environment variable and its value. -class ResolvedEnvironmentVariable extends $pb.GeneratedMessage { - factory ResolvedEnvironmentVariable({ +class ResolvedVariable extends $pb.GeneratedMessage { + factory ResolvedVariable({ $core.String? name, $core.String? value, }) { @@ -780,16 +746,16 @@ class ResolvedEnvironmentVariable extends $pb.GeneratedMessage { } return $result; } - ResolvedEnvironmentVariable._() : super(); - factory ResolvedEnvironmentVariable.fromBuffer($core.List<$core.int> i, + ResolvedVariable._() : super(); + factory ResolvedVariable.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory ResolvedEnvironmentVariable.fromJson($core.String i, + factory ResolvedVariable.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'ResolvedEnvironmentVariable', + _omitMessageNames ? '' : 'ResolvedVariable', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'name') @@ -799,29 +765,25 @@ class ResolvedEnvironmentVariable extends $pb.GeneratedMessage { @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - ResolvedEnvironmentVariable clone() => - ResolvedEnvironmentVariable()..mergeFromMessage(this); + ResolvedVariable clone() => ResolvedVariable()..mergeFromMessage(this); @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - ResolvedEnvironmentVariable copyWith( - void Function(ResolvedEnvironmentVariable) updates) => - super.copyWith( - (message) => updates(message as ResolvedEnvironmentVariable)) - as ResolvedEnvironmentVariable; + ResolvedVariable copyWith(void Function(ResolvedVariable) updates) => + super.copyWith((message) => updates(message as ResolvedVariable)) + as ResolvedVariable; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static ResolvedEnvironmentVariable create() => - ResolvedEnvironmentVariable._(); - ResolvedEnvironmentVariable createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); + static ResolvedVariable create() => ResolvedVariable._(); + ResolvedVariable createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); @$core.pragma('dart2js:noInline') - static ResolvedEnvironmentVariable getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static ResolvedEnvironmentVariable? _defaultInstance; + static ResolvedVariable getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedVariable? _defaultInstance; /// The name of the environment variable. @$pb.TagNumber(1) @@ -1011,7 +973,7 @@ enum ResolvedAuthProvider_Config { /// A resolved auth provider configuration. class ResolvedAuthProvider extends $pb.GeneratedMessage { factory ResolvedAuthProvider({ - $core.String? id, + $core.String? authProviderId, ResolvedAuthProvider_Type? type, ResolvedEmailOtpProviderConfig? emailOtp, ResolvedSmsOtpProviderConfig? smsOtp, @@ -1020,8 +982,8 @@ class ResolvedAuthProvider extends $pb.GeneratedMessage { ResolvedAppleOAuthProviderConfig? apple, }) { final $result = create(); - if (id != null) { - $result.id = id; + if (authProviderId != null) { + $result.authProviderId = authProviderId; } if (type != null) { $result.type = type; @@ -1065,7 +1027,7 @@ class ResolvedAuthProvider extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) ..oo(0, [3, 4, 5, 6, 7]) - ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(1, _omitFieldNames ? '' : 'authProviderId') ..e( 2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: @@ -1114,16 +1076,16 @@ class ResolvedAuthProvider extends $pb.GeneratedMessage { /// The ID of the auth provider. @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); + $core.String get authProviderId => $_getSZ(0); @$pb.TagNumber(1) - set id($core.String v) { + set authProviderId($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); + $core.bool hasAuthProviderId() => $_has(0); @$pb.TagNumber(1) - void clearId() => clearField(1); + void clearAuthProviderId() => clearField(1); /// The type of the auth provider. @$pb.TagNumber(2) @@ -1627,14 +1589,14 @@ enum ResolvedExternalAuthProvider_Config { firebase, supabase, notSet } /// A resolved external auth provider configuration. class ResolvedExternalAuthProvider extends $pb.GeneratedMessage { factory ResolvedExternalAuthProvider({ - $core.String? id, + $core.String? authProviderId, ResolvedExternalAuthProvider_Type? type, ResolvedFirebaseExternalAuthProviderConfig? firebase, ResolvedSupabaseExternalAuthProviderConfig? supabase, }) { final $result = create(); - if (id != null) { - $result.id = id; + if (authProviderId != null) { + $result.authProviderId = authProviderId; } if (type != null) { $result.type = type; @@ -1666,7 +1628,7 @@ class ResolvedExternalAuthProvider extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) ..oo(0, [3, 4]) - ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(1, _omitFieldNames ? '' : 'authProviderId') ..e( 2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: ResolvedExternalAuthProvider_Type @@ -1714,16 +1676,16 @@ class ResolvedExternalAuthProvider extends $pb.GeneratedMessage { /// The ID of the external auth provider. @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); + $core.String get authProviderId => $_getSZ(0); @$pb.TagNumber(1) - set id($core.String v) { + set authProviderId($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); + $core.bool hasAuthProviderId() => $_has(0); @$pb.TagNumber(1) - void clearId() => clearField(1); + void clearAuthProviderId() => clearField(1); /// The type of the external auth provider. @$pb.TagNumber(2) @@ -1772,7 +1734,7 @@ class ResolvedExternalAuthProvider extends $pb.GeneratedMessage { /// The configuration of an external Firebase auth provider. class ResolvedFirebaseExternalAuthProviderConfig extends $pb.GeneratedMessage { factory ResolvedFirebaseExternalAuthProviderConfig({ - ResolvedEnvironmentVariable? projectId, + ResolvedVariable? projectId, }) { final $result = create(); if (projectId != null) { @@ -1793,8 +1755,8 @@ class ResolvedFirebaseExternalAuthProviderConfig extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedFirebaseExternalAuthProviderConfig', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOM(1, _omitFieldNames ? '' : 'projectId', - subBuilder: ResolvedEnvironmentVariable.create) + ..aOM(1, _omitFieldNames ? '' : 'projectId', + subBuilder: ResolvedVariable.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -1828,9 +1790,9 @@ class ResolvedFirebaseExternalAuthProviderConfig extends $pb.GeneratedMessage { /// The Firebase project ID. @$pb.TagNumber(1) - ResolvedEnvironmentVariable get projectId => $_getN(0); + ResolvedVariable get projectId => $_getN(0); @$pb.TagNumber(1) - set projectId(ResolvedEnvironmentVariable v) { + set projectId(ResolvedVariable v) { setField(1, v); } @@ -1839,13 +1801,13 @@ class ResolvedFirebaseExternalAuthProviderConfig extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearProjectId() => clearField(1); @$pb.TagNumber(1) - ResolvedEnvironmentVariable ensureProjectId() => $_ensure(0); + ResolvedVariable ensureProjectId() => $_ensure(0); } /// The configuration of an external Supabase auth provider. class ResolvedSupabaseExternalAuthProviderConfig extends $pb.GeneratedMessage { factory ResolvedSupabaseExternalAuthProviderConfig({ - ResolvedEnvironmentVariable? projectUrl, + ResolvedVariable? projectUrl, ResolvedSecret? jwtSecret, }) { final $result = create(); @@ -1870,8 +1832,8 @@ class ResolvedSupabaseExternalAuthProviderConfig extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'ResolvedSupabaseExternalAuthProviderConfig', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..aOM(1, _omitFieldNames ? '' : 'projectUrl', - subBuilder: ResolvedEnvironmentVariable.create) + ..aOM(1, _omitFieldNames ? '' : 'projectUrl', + subBuilder: ResolvedVariable.create) ..aOM(2, _omitFieldNames ? '' : 'jwtSecret', subBuilder: ResolvedSecret.create) ..hasRequiredFields = false; @@ -1907,9 +1869,9 @@ class ResolvedSupabaseExternalAuthProviderConfig extends $pb.GeneratedMessage { /// Required. The Supabase project URL. @$pb.TagNumber(1) - ResolvedEnvironmentVariable get projectUrl => $_getN(0); + ResolvedVariable get projectUrl => $_getN(0); @$pb.TagNumber(1) - set projectUrl(ResolvedEnvironmentVariable v) { + set projectUrl(ResolvedVariable v) { setField(1, v); } @@ -1918,7 +1880,7 @@ class ResolvedSupabaseExternalAuthProviderConfig extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearProjectUrl() => clearField(1); @$pb.TagNumber(1) - ResolvedEnvironmentVariable ensureProjectUrl() => $_ensure(0); + ResolvedVariable ensureProjectUrl() => $_ensure(0); /// Optional. The Supabase JWT secret. @$pb.TagNumber(2) @@ -1936,241 +1898,584 @@ class ResolvedSupabaseExternalAuthProviderConfig extends $pb.GeneratedMessage { ResolvedSecret ensureJwtSecret() => $_ensure(1); } -/// Information about the Dart or Flutter SDK used to deploy the project. -class SdkInfo extends $pb.GeneratedMessage { - factory SdkInfo({ - SdkType? type, - Version? version, - $core.Iterable<$core.String>? enabledExperiments, +enum ResolvedDatabase_Config { celest, notSet } + +/// The resolved AST of a Celest database. +class ResolvedDatabase extends $pb.GeneratedMessage { + factory ResolvedDatabase({ + $core.String? databaseId, + ResolvedDatabase_Type? type, + ResolvedDatabaseSchema? schema, + ResolvedCelestDatabaseConfig? celest, }) { final $result = create(); + if (databaseId != null) { + $result.databaseId = databaseId; + } if (type != null) { $result.type = type; } - if (version != null) { - $result.version = version; + if (schema != null) { + $result.schema = schema; } - if (enabledExperiments != null) { - $result.enabledExperiments.addAll(enabledExperiments); + if (celest != null) { + $result.celest = celest; } return $result; } - SdkInfo._() : super(); - factory SdkInfo.fromBuffer($core.List<$core.int> i, + ResolvedDatabase._() : super(); + factory ResolvedDatabase.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory SdkInfo.fromJson($core.String i, + factory ResolvedDatabase.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + static const $core.Map<$core.int, ResolvedDatabase_Config> + _ResolvedDatabase_ConfigByTag = { + 4: ResolvedDatabase_Config.celest, + 0: ResolvedDatabase_Config.notSet + }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'SdkInfo', + _omitMessageNames ? '' : 'ResolvedDatabase', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..e(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, - defaultOrMaker: SdkType.SDK_TYPE_UNSPECIFIED, - valueOf: SdkType.valueOf, - enumValues: SdkType.values) - ..aOM(2, _omitFieldNames ? '' : 'version', - subBuilder: Version.create) - ..pPS(3, _omitFieldNames ? '' : 'enabledExperiments') + ..oo(0, [4]) + ..aOS(1, _omitFieldNames ? '' : 'databaseId') + ..e( + 2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, + defaultOrMaker: ResolvedDatabase_Type.DATABASE_TYPE_UNSPECIFIED, + valueOf: ResolvedDatabase_Type.valueOf, + enumValues: ResolvedDatabase_Type.values) + ..aOM(3, _omitFieldNames ? '' : 'schema', + subBuilder: ResolvedDatabaseSchema.create) + ..aOM(4, _omitFieldNames ? '' : 'celest', + subBuilder: ResolvedCelestDatabaseConfig.create) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - SdkInfo clone() => SdkInfo()..mergeFromMessage(this); + ResolvedDatabase clone() => ResolvedDatabase()..mergeFromMessage(this); @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - SdkInfo copyWith(void Function(SdkInfo) updates) => - super.copyWith((message) => updates(message as SdkInfo)) as SdkInfo; + ResolvedDatabase copyWith(void Function(ResolvedDatabase) updates) => + super.copyWith((message) => updates(message as ResolvedDatabase)) + as ResolvedDatabase; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static SdkInfo create() => SdkInfo._(); - SdkInfo createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static ResolvedDatabase create() => ResolvedDatabase._(); + ResolvedDatabase createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); @$core.pragma('dart2js:noInline') - static SdkInfo getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static SdkInfo? _defaultInstance; + static ResolvedDatabase getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedDatabase? _defaultInstance; + + ResolvedDatabase_Config whichConfig() => + _ResolvedDatabase_ConfigByTag[$_whichOneof(0)]!; + void clearConfig() => clearField($_whichOneof(0)); + + /// The unique identifier of the database within a project. + @$pb.TagNumber(1) + $core.String get databaseId => $_getSZ(0); + @$pb.TagNumber(1) + set databaseId($core.String v) { + $_setString(0, v); + } - /// The type of the SDK. @$pb.TagNumber(1) - SdkType get type => $_getN(0); + $core.bool hasDatabaseId() => $_has(0); @$pb.TagNumber(1) - set type(SdkType v) { + void clearDatabaseId() => clearField(1); + + /// The type of the database. + @$pb.TagNumber(2) + ResolvedDatabase_Type get type => $_getN(1); + @$pb.TagNumber(2) + set type(ResolvedDatabase_Type v) { + setField(2, v); + } + + @$pb.TagNumber(2) + $core.bool hasType() => $_has(1); + @$pb.TagNumber(2) + void clearType() => clearField(2); + + /// The schema of the database. + @$pb.TagNumber(3) + ResolvedDatabaseSchema get schema => $_getN(2); + @$pb.TagNumber(3) + set schema(ResolvedDatabaseSchema v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasSchema() => $_has(2); + @$pb.TagNumber(3) + void clearSchema() => clearField(3); + @$pb.TagNumber(3) + ResolvedDatabaseSchema ensureSchema() => $_ensure(2); + + /// The provider configuration of a Celest database. + @$pb.TagNumber(4) + ResolvedCelestDatabaseConfig get celest => $_getN(3); + @$pb.TagNumber(4) + set celest(ResolvedCelestDatabaseConfig v) { + setField(4, v); + } + + @$pb.TagNumber(4) + $core.bool hasCelest() => $_has(3); + @$pb.TagNumber(4) + void clearCelest() => clearField(4); + @$pb.TagNumber(4) + ResolvedCelestDatabaseConfig ensureCelest() => $_ensure(3); +} + +/// The configuration of a Celest database. +class ResolvedCelestDatabaseConfig extends $pb.GeneratedMessage { + factory ResolvedCelestDatabaseConfig({ + ResolvedVariable? hostname, + ResolvedSecret? token, + }) { + final $result = create(); + if (hostname != null) { + $result.hostname = hostname; + } + if (token != null) { + $result.token = token; + } + return $result; + } + ResolvedCelestDatabaseConfig._() : super(); + factory ResolvedCelestDatabaseConfig.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory ResolvedCelestDatabaseConfig.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ResolvedCelestDatabaseConfig', + package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'hostname', + subBuilder: ResolvedVariable.create) + ..aOM(2, _omitFieldNames ? '' : 'token', + subBuilder: ResolvedSecret.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ResolvedCelestDatabaseConfig clone() => + ResolvedCelestDatabaseConfig()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ResolvedCelestDatabaseConfig copyWith( + void Function(ResolvedCelestDatabaseConfig) updates) => + super.copyWith( + (message) => updates(message as ResolvedCelestDatabaseConfig)) + as ResolvedCelestDatabaseConfig; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ResolvedCelestDatabaseConfig create() => + ResolvedCelestDatabaseConfig._(); + ResolvedCelestDatabaseConfig createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ResolvedCelestDatabaseConfig getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedCelestDatabaseConfig? _defaultInstance; + + /// Required. The database hostname. + @$pb.TagNumber(1) + ResolvedVariable get hostname => $_getN(0); + @$pb.TagNumber(1) + set hostname(ResolvedVariable v) { setField(1, v); } @$pb.TagNumber(1) - $core.bool hasType() => $_has(0); + $core.bool hasHostname() => $_has(0); @$pb.TagNumber(1) - void clearType() => clearField(1); + void clearHostname() => clearField(1); + @$pb.TagNumber(1) + ResolvedVariable ensureHostname() => $_ensure(0); - /// The version of the SDK. + /// Required. The database token. @$pb.TagNumber(2) - Version get version => $_getN(1); + ResolvedSecret get token => $_getN(1); @$pb.TagNumber(2) - set version(Version v) { + set token(ResolvedSecret v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasVersion() => $_has(1); + $core.bool hasToken() => $_has(1); + @$pb.TagNumber(2) + void clearToken() => clearField(2); + @$pb.TagNumber(2) + ResolvedSecret ensureToken() => $_ensure(1); +} + +enum ResolvedDatabaseSchema_Schema { drift, notSet } + +/// The resolved AST of a Celest database schema. +class ResolvedDatabaseSchema extends $pb.GeneratedMessage { + factory ResolvedDatabaseSchema({ + $core.String? databaseSchemaId, + ResolvedDatabaseSchema_Type? type, + ResolvedDriftDatabaseSchema? drift, + }) { + final $result = create(); + if (databaseSchemaId != null) { + $result.databaseSchemaId = databaseSchemaId; + } + if (type != null) { + $result.type = type; + } + if (drift != null) { + $result.drift = drift; + } + return $result; + } + ResolvedDatabaseSchema._() : super(); + factory ResolvedDatabaseSchema.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory ResolvedDatabaseSchema.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static const $core.Map<$core.int, ResolvedDatabaseSchema_Schema> + _ResolvedDatabaseSchema_SchemaByTag = { + 3: ResolvedDatabaseSchema_Schema.drift, + 0: ResolvedDatabaseSchema_Schema.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ResolvedDatabaseSchema', + package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), + createEmptyInstance: create) + ..oo(0, [3]) + ..aOS(1, _omitFieldNames ? '' : 'databaseSchemaId') + ..e( + 2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, + defaultOrMaker: + ResolvedDatabaseSchema_Type.DATABASE_SCHEMA_TYPE_UNSPECIFIED, + valueOf: ResolvedDatabaseSchema_Type.valueOf, + enumValues: ResolvedDatabaseSchema_Type.values) + ..aOM(3, _omitFieldNames ? '' : 'drift', + subBuilder: ResolvedDriftDatabaseSchema.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ResolvedDatabaseSchema clone() => + ResolvedDatabaseSchema()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ResolvedDatabaseSchema copyWith( + void Function(ResolvedDatabaseSchema) updates) => + super.copyWith((message) => updates(message as ResolvedDatabaseSchema)) + as ResolvedDatabaseSchema; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ResolvedDatabaseSchema create() => ResolvedDatabaseSchema._(); + ResolvedDatabaseSchema createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ResolvedDatabaseSchema getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedDatabaseSchema? _defaultInstance; + + ResolvedDatabaseSchema_Schema whichSchema() => + _ResolvedDatabaseSchema_SchemaByTag[$_whichOneof(0)]!; + void clearSchema() => clearField($_whichOneof(0)); + + /// The unique identifier of the database schema within a project. + @$pb.TagNumber(1) + $core.String get databaseSchemaId => $_getSZ(0); + @$pb.TagNumber(1) + set databaseSchemaId($core.String v) { + $_setString(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasDatabaseSchemaId() => $_has(0); + @$pb.TagNumber(1) + void clearDatabaseSchemaId() => clearField(1); + + /// The type of the database schema. + @$pb.TagNumber(2) + ResolvedDatabaseSchema_Type get type => $_getN(1); @$pb.TagNumber(2) - void clearVersion() => clearField(2); + set type(ResolvedDatabaseSchema_Type v) { + setField(2, v); + } + @$pb.TagNumber(2) - Version ensureVersion() => $_ensure(1); + $core.bool hasType() => $_has(1); + @$pb.TagNumber(2) + void clearType() => clearField(2); - /// The Dart experiments enabled for the project. + /// The Drift database schema. @$pb.TagNumber(3) - $core.List<$core.String> get enabledExperiments => $_getList(2); + ResolvedDriftDatabaseSchema get drift => $_getN(2); + @$pb.TagNumber(3) + set drift(ResolvedDriftDatabaseSchema v) { + setField(3, v); + } + + @$pb.TagNumber(3) + $core.bool hasDrift() => $_has(2); + @$pb.TagNumber(3) + void clearDrift() => clearField(3); + @$pb.TagNumber(3) + ResolvedDriftDatabaseSchema ensureDrift() => $_ensure(2); } -/// A semantic version, broken down by its components. -class Version extends $pb.GeneratedMessage { - factory Version({ - $core.int? major, - $core.int? minor, - $core.int? patch, - $core.Iterable<$22.Value>? preRelease, - $core.Iterable<$22.Value>? build, - $core.String? canonicalizedVersion, +/// The resolved AST of a Drift database schema. +class ResolvedDriftDatabaseSchema extends $pb.GeneratedMessage { + factory ResolvedDriftDatabaseSchema({ + $core.int? version, + $22.Struct? schemaJson, }) { final $result = create(); - if (major != null) { - $result.major = major; + if (version != null) { + $result.version = version; } - if (minor != null) { - $result.minor = minor; + if (schemaJson != null) { + $result.schemaJson = schemaJson; + } + return $result; + } + ResolvedDriftDatabaseSchema._() : super(); + factory ResolvedDriftDatabaseSchema.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory ResolvedDriftDatabaseSchema.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ResolvedDriftDatabaseSchema', + package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), + createEmptyInstance: create) + ..a<$core.int>(1, _omitFieldNames ? '' : 'version', $pb.PbFieldType.O3) + ..aOM<$22.Struct>(2, _omitFieldNames ? '' : 'schemaJson', + subBuilder: $22.Struct.create) + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ResolvedDriftDatabaseSchema clone() => + ResolvedDriftDatabaseSchema()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ResolvedDriftDatabaseSchema copyWith( + void Function(ResolvedDriftDatabaseSchema) updates) => + super.copyWith( + (message) => updates(message as ResolvedDriftDatabaseSchema)) + as ResolvedDriftDatabaseSchema; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ResolvedDriftDatabaseSchema create() => + ResolvedDriftDatabaseSchema._(); + ResolvedDriftDatabaseSchema createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ResolvedDriftDatabaseSchema getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ResolvedDriftDatabaseSchema? _defaultInstance; + + /// Required. The Drift schema version. + @$pb.TagNumber(1) + $core.int get version => $_getIZ(0); + @$pb.TagNumber(1) + set version($core.int v) { + $_setSignedInt32(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasVersion() => $_has(0); + @$pb.TagNumber(1) + void clearVersion() => clearField(1); + + /// Required. The Drift schema JSON. + @$pb.TagNumber(2) + $22.Struct get schemaJson => $_getN(1); + @$pb.TagNumber(2) + set schemaJson($22.Struct v) { + setField(2, v); + } + + @$pb.TagNumber(2) + $core.bool hasSchemaJson() => $_has(1); + @$pb.TagNumber(2) + void clearSchemaJson() => clearField(2); + @$pb.TagNumber(2) + $22.Struct ensureSchemaJson() => $_ensure(1); +} + +/// Configuration of the SDKs used to deploy the project. +class SdkConfiguration extends $pb.GeneratedMessage { + factory SdkConfiguration({ + $53.Version? celest, + $53.Sdk? dart, + $53.Sdk? flutter, + $53.SdkType? targetSdk, + $core.Iterable<$54.FeatureFlag>? featureFlags, + }) { + final $result = create(); + if (celest != null) { + $result.celest = celest; } - if (patch != null) { - $result.patch = patch; + if (dart != null) { + $result.dart = dart; } - if (preRelease != null) { - $result.preRelease.addAll(preRelease); + if (flutter != null) { + $result.flutter = flutter; } - if (build != null) { - $result.build.addAll(build); + if (targetSdk != null) { + $result.targetSdk = targetSdk; } - if (canonicalizedVersion != null) { - $result.canonicalizedVersion = canonicalizedVersion; + if (featureFlags != null) { + $result.featureFlags.addAll(featureFlags); } return $result; } - Version._() : super(); - factory Version.fromBuffer($core.List<$core.int> i, + SdkConfiguration._() : super(); + factory SdkConfiguration.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory Version.fromJson($core.String i, + factory SdkConfiguration.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'Version', + _omitMessageNames ? '' : 'SdkConfiguration', package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), createEmptyInstance: create) - ..a<$core.int>(1, _omitFieldNames ? '' : 'major', $pb.PbFieldType.O3) - ..a<$core.int>(2, _omitFieldNames ? '' : 'minor', $pb.PbFieldType.O3) - ..a<$core.int>(3, _omitFieldNames ? '' : 'patch', $pb.PbFieldType.O3) - ..pc<$22.Value>(4, _omitFieldNames ? '' : 'preRelease', $pb.PbFieldType.PM, - subBuilder: $22.Value.create) - ..pc<$22.Value>(5, _omitFieldNames ? '' : 'build', $pb.PbFieldType.PM, - subBuilder: $22.Value.create) - ..aOS(6, _omitFieldNames ? '' : 'canonicalizedVersion') + ..aOM<$53.Version>(1, _omitFieldNames ? '' : 'celest', + subBuilder: $53.Version.create) + ..aOM<$53.Sdk>(2, _omitFieldNames ? '' : 'dart', subBuilder: $53.Sdk.create) + ..aOM<$53.Sdk>(3, _omitFieldNames ? '' : 'flutter', + subBuilder: $53.Sdk.create) + ..e<$53.SdkType>(4, _omitFieldNames ? '' : 'targetSdk', $pb.PbFieldType.OE, + defaultOrMaker: $53.SdkType.SDK_TYPE_UNSPECIFIED, + valueOf: $53.SdkType.valueOf, + enumValues: $53.SdkType.values) + ..pc<$54.FeatureFlag>( + 5, _omitFieldNames ? '' : 'featureFlags', $pb.PbFieldType.KE, + valueOf: $54.FeatureFlag.valueOf, + enumValues: $54.FeatureFlag.values, + defaultEnumValue: $54.FeatureFlag.FEATURE_FLAG_UNSPECIFIED) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - Version clone() => Version()..mergeFromMessage(this); + SdkConfiguration clone() => SdkConfiguration()..mergeFromMessage(this); @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - Version copyWith(void Function(Version) updates) => - super.copyWith((message) => updates(message as Version)) as Version; + SdkConfiguration copyWith(void Function(SdkConfiguration) updates) => + super.copyWith((message) => updates(message as SdkConfiguration)) + as SdkConfiguration; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static Version create() => Version._(); - Version createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static SdkConfiguration create() => SdkConfiguration._(); + SdkConfiguration createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); @$core.pragma('dart2js:noInline') - static Version getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static Version? _defaultInstance; + static SdkConfiguration getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SdkConfiguration? _defaultInstance; - /// The major version number: "1" in "1.2.3". + /// The Celest version used to deploy the project. @$pb.TagNumber(1) - $core.int get major => $_getIZ(0); + $53.Version get celest => $_getN(0); @$pb.TagNumber(1) - set major($core.int v) { - $_setSignedInt32(0, v); + set celest($53.Version v) { + setField(1, v); } @$pb.TagNumber(1) - $core.bool hasMajor() => $_has(0); + $core.bool hasCelest() => $_has(0); @$pb.TagNumber(1) - void clearMajor() => clearField(1); + void clearCelest() => clearField(1); + @$pb.TagNumber(1) + $53.Version ensureCelest() => $_ensure(0); - /// The minor version number: "2" in "1.2.3". + /// The Dart SDK used to deploy the project. @$pb.TagNumber(2) - $core.int get minor => $_getIZ(1); + $53.Sdk get dart => $_getN(1); @$pb.TagNumber(2) - set minor($core.int v) { - $_setSignedInt32(1, v); + set dart($53.Sdk v) { + setField(2, v); } @$pb.TagNumber(2) - $core.bool hasMinor() => $_has(1); + $core.bool hasDart() => $_has(1); + @$pb.TagNumber(2) + void clearDart() => clearField(2); @$pb.TagNumber(2) - void clearMinor() => clearField(2); + $53.Sdk ensureDart() => $_ensure(1); - /// The patch version number: "3" in "1.2.3". + /// The Flutter SDK used to deploy the project, if any. @$pb.TagNumber(3) - $core.int get patch => $_getIZ(2); + $53.Sdk get flutter => $_getN(2); @$pb.TagNumber(3) - set patch($core.int v) { - $_setSignedInt32(2, v); + set flutter($53.Sdk v) { + setField(3, v); } @$pb.TagNumber(3) - $core.bool hasPatch() => $_has(2); + $core.bool hasFlutter() => $_has(2); + @$pb.TagNumber(3) + void clearFlutter() => clearField(3); @$pb.TagNumber(3) - void clearPatch() => clearField(3); + $53.Sdk ensureFlutter() => $_ensure(2); - /// The pre-release identifier: "foo" in "1.2.3-foo". - /// - /// This is split into a list of components, each of which may be either a - /// string or a non-negative integer. It may also be empty, indicating that - /// this version has no pre-release identifier. + /// The target SDK for deployment. @$pb.TagNumber(4) - $core.List<$22.Value> get preRelease => $_getList(3); - - /// The build identifier: "foo" in "1.2.3+foo". - /// - /// This is split into a list of components, each of which may be either a - /// string or a non-negative integer. It may also be empty, indicating that - /// this version has no build identifier. - @$pb.TagNumber(5) - $core.List<$22.Value> get build => $_getList(4); - - /// The canonicalized version string. - @$pb.TagNumber(6) - $core.String get canonicalizedVersion => $_getSZ(5); - @$pb.TagNumber(6) - set canonicalizedVersion($core.String v) { - $_setString(5, v); + $53.SdkType get targetSdk => $_getN(3); + @$pb.TagNumber(4) + set targetSdk($53.SdkType v) { + setField(4, v); } - @$pb.TagNumber(6) - $core.bool hasCanonicalizedVersion() => $_has(5); - @$pb.TagNumber(6) - void clearCanonicalizedVersion() => clearField(6); + @$pb.TagNumber(4) + $core.bool hasTargetSdk() => $_has(3); + @$pb.TagNumber(4) + void clearTargetSdk() => clearField(4); + + /// The feature flags enabled by the project. + @$pb.TagNumber(5) + $core.List<$54.FeatureFlag> get featureFlags => $_getList(4); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbenum.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbenum.dart index 77c981af..d5e6b2d9 100644 --- a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbenum.dart +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbenum.dart @@ -13,24 +13,33 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -/// The type of SDK. -class SdkType extends $pb.ProtobufEnum { - static const SdkType SDK_TYPE_UNSPECIFIED = - SdkType._(0, _omitEnumNames ? '' : 'SDK_TYPE_UNSPECIFIED'); - static const SdkType DART = SdkType._(1, _omitEnumNames ? '' : 'DART'); - static const SdkType FLUTTER = SdkType._(2, _omitEnumNames ? '' : 'FLUTTER'); - - static const $core.List values = [ - SDK_TYPE_UNSPECIFIED, - DART, - FLUTTER, +/// The type of the stream configuration. +class ResolvedStreamConfig_Type extends $pb.ProtobufEnum { + static const ResolvedStreamConfig_Type STREAM_CONFIG_TYPE_UNSPECIFIED = + ResolvedStreamConfig_Type._( + 0, _omitEnumNames ? '' : 'STREAM_CONFIG_TYPE_UNSPECIFIED'); + static const ResolvedStreamConfig_Type UNIDIRECTIONAL_CLIENT = + ResolvedStreamConfig_Type._( + 1, _omitEnumNames ? '' : 'UNIDIRECTIONAL_CLIENT'); + static const ResolvedStreamConfig_Type UNIDIRECTIONAL_SERVER = + ResolvedStreamConfig_Type._( + 2, _omitEnumNames ? '' : 'UNIDIRECTIONAL_SERVER'); + static const ResolvedStreamConfig_Type BIDIRECTIONAL = + ResolvedStreamConfig_Type._(3, _omitEnumNames ? '' : 'BIDIRECTIONAL'); + + static const $core.List values = + [ + STREAM_CONFIG_TYPE_UNSPECIFIED, + UNIDIRECTIONAL_CLIENT, + UNIDIRECTIONAL_SERVER, + BIDIRECTIONAL, ]; - static final $core.Map<$core.int, SdkType> _byValue = + static final $core.Map<$core.int, ResolvedStreamConfig_Type> _byValue = $pb.ProtobufEnum.initByValue(values); - static SdkType? valueOf($core.int value) => _byValue[value]; + static ResolvedStreamConfig_Type? valueOf($core.int value) => _byValue[value]; - const SdkType._($core.int v, $core.String n) : super(v, n); + const ResolvedStreamConfig_Type._($core.int v, $core.String n) : super(v, n); } /// The type of an auth provider. @@ -93,4 +102,48 @@ class ResolvedExternalAuthProvider_Type extends $pb.ProtobufEnum { : super(v, n); } +/// The type of the database. +class ResolvedDatabase_Type extends $pb.ProtobufEnum { + static const ResolvedDatabase_Type DATABASE_TYPE_UNSPECIFIED = + ResolvedDatabase_Type._( + 0, _omitEnumNames ? '' : 'DATABASE_TYPE_UNSPECIFIED'); + static const ResolvedDatabase_Type CELEST = + ResolvedDatabase_Type._(1, _omitEnumNames ? '' : 'CELEST'); + + static const $core.List values = + [ + DATABASE_TYPE_UNSPECIFIED, + CELEST, + ]; + + static final $core.Map<$core.int, ResolvedDatabase_Type> _byValue = + $pb.ProtobufEnum.initByValue(values); + static ResolvedDatabase_Type? valueOf($core.int value) => _byValue[value]; + + const ResolvedDatabase_Type._($core.int v, $core.String n) : super(v, n); +} + +/// The type of the database schema. +class ResolvedDatabaseSchema_Type extends $pb.ProtobufEnum { + static const ResolvedDatabaseSchema_Type DATABASE_SCHEMA_TYPE_UNSPECIFIED = + ResolvedDatabaseSchema_Type._( + 0, _omitEnumNames ? '' : 'DATABASE_SCHEMA_TYPE_UNSPECIFIED'); + static const ResolvedDatabaseSchema_Type DRIFT = + ResolvedDatabaseSchema_Type._(1, _omitEnumNames ? '' : 'DRIFT'); + + static const $core.List values = + [ + DATABASE_SCHEMA_TYPE_UNSPECIFIED, + DRIFT, + ]; + + static final $core.Map<$core.int, ResolvedDatabaseSchema_Type> _byValue = + $pb.ProtobufEnum.initByValue(values); + static ResolvedDatabaseSchema_Type? valueOf($core.int value) => + _byValue[value]; + + const ResolvedDatabaseSchema_Type._($core.int v, $core.String n) + : super(v, n); +} + const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart index 520149e7..02fd8cd5 100644 --- a/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/resolved_ast.pbjson.dart @@ -13,82 +13,75 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; -@$core.Deprecated('Use sdkTypeDescriptor instead') -const SdkType$json = { - '1': 'SdkType', - '2': [ - {'1': 'SDK_TYPE_UNSPECIFIED', '2': 0}, - {'1': 'DART', '2': 1}, - {'1': 'FLUTTER', '2': 2}, - ], -}; - -/// Descriptor for `SdkType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List sdkTypeDescriptor = $convert.base64Decode( - 'CgdTZGtUeXBlEhgKFFNES19UWVBFX1VOU1BFQ0lGSUVEEAASCAoEREFSVBABEgsKB0ZMVVRURV' - 'IQAg=='); - @$core.Deprecated('Use resolvedProjectDescriptor instead') const ResolvedProject$json = { '1': 'ResolvedProject', '2': [ - {'1': 'name', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'name'}, + {'1': 'project_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'projectId'}, { - '1': 'apis', + '1': 'environment_id', '3': 2, - '4': 3, - '5': 11, - '6': '.celest.ast.v1.ResolvedProject.ApisEntry', + '4': 1, + '5': 9, '8': {}, - '10': 'apis' + '10': 'environmentId' }, { - '1': 'environment_variables', + '1': 'apis', '3': 3, '4': 3, '5': 11, - '6': '.celest.ast.v1.ResolvedEnvironmentVariable', + '6': '.celest.ast.v1.ResolvedProject.ApisEntry', '8': {}, - '10': 'environmentVariables' + '10': 'apis' }, { - '1': 'auth', + '1': 'variables', '3': 4, - '4': 1, + '4': 3, '5': 11, - '6': '.celest.ast.v1.ResolvedAuth', + '6': '.celest.ast.v1.ResolvedVariable', '8': {}, - '10': 'auth' + '10': 'variables' }, { - '1': 'sdk', + '1': 'secrets', '3': 5, - '4': 1, + '4': 3, '5': 11, - '6': '.celest.ast.v1.SdkInfo', + '6': '.celest.ast.v1.ResolvedSecret', '8': {}, - '10': 'sdk' + '10': 'secrets' }, { - '1': 'feature_flags', + '1': 'auth', '3': 6, - '4': 3, - '5': 14, - '6': '.celest.ast.v1.FeatureFlag', + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedAuth', '8': {}, - '10': 'featureFlags' + '10': 'auth' }, { - '1': 'secrets', + '1': 'databases', '3': 7, '4': 3, '5': 11, - '6': '.celest.ast.v1.ResolvedSecret', + '6': '.celest.ast.v1.ResolvedProject.DatabasesEntry', '8': {}, - '10': 'secrets' + '10': 'databases' + }, + { + '1': 'sdk_config', + '3': 99, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.SdkConfiguration', + '8': {}, + '10': 'sdkConfig' }, ], - '3': [ResolvedProject_ApisEntry$json], + '3': [ResolvedProject_ApisEntry$json, ResolvedProject_DatabasesEntry$json], }; @$core.Deprecated('Use resolvedProjectDescriptor instead') @@ -108,18 +101,38 @@ const ResolvedProject_ApisEntry$json = { '7': {'7': true}, }; +@$core.Deprecated('Use resolvedProjectDescriptor instead') +const ResolvedProject_DatabasesEntry$json = { + '1': 'DatabasesEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + { + '1': 'value', + '3': 2, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedDatabase', + '10': 'value' + }, + ], + '7': {'7': true}, +}; + /// Descriptor for `ResolvedProject`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedProjectDescriptor = $convert.base64Decode( - 'Cg9SZXNvbHZlZFByb2plY3QSFwoEbmFtZRgBIAEoCUID4EECUgRuYW1lEkEKBGFwaXMYAiADKA' - 'syKC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkUHJvamVjdC5BcGlzRW50cnlCA+BBAVIEYXBpcxJk' - 'ChVlbnZpcm9ubWVudF92YXJpYWJsZXMYAyADKAsyKi5jZWxlc3QuYXN0LnYxLlJlc29sdmVkRW' - '52aXJvbm1lbnRWYXJpYWJsZUID4EEBUhRlbnZpcm9ubWVudFZhcmlhYmxlcxI0CgRhdXRoGAQg' - 'ASgLMhsuY2VsZXN0LmFzdC52MS5SZXNvbHZlZEF1dGhCA+BBAVIEYXV0aBItCgNzZGsYBSABKA' - 'syFi5jZWxlc3QuYXN0LnYxLlNka0luZm9CA+BBAlIDc2RrEkQKDWZlYXR1cmVfZmxhZ3MYBiAD' - 'KA4yGi5jZWxlc3QuYXN0LnYxLkZlYXR1cmVGbGFnQgPgQQFSDGZlYXR1cmVGbGFncxI8CgdzZW' - 'NyZXRzGAcgAygLMh0uY2VsZXN0LmFzdC52MS5SZXNvbHZlZFNlY3JldEID4EEBUgdzZWNyZXRz' - 'GlMKCUFwaXNFbnRyeRIQCgNrZXkYASABKAlSA2tleRIwCgV2YWx1ZRgCIAEoCzIaLmNlbGVzdC' - '5hc3QudjEuUmVzb2x2ZWRBcGlSBXZhbHVlOgI4AQ=='); + 'Cg9SZXNvbHZlZFByb2plY3QSIgoKcHJvamVjdF9pZBgBIAEoCUID4EECUglwcm9qZWN0SWQSKg' + 'oOZW52aXJvbm1lbnRfaWQYAiABKAlCA+BBAlINZW52aXJvbm1lbnRJZBJBCgRhcGlzGAMgAygL' + 'MiguY2VsZXN0LmFzdC52MS5SZXNvbHZlZFByb2plY3QuQXBpc0VudHJ5QgPgQQFSBGFwaXMSQg' + 'oJdmFyaWFibGVzGAQgAygLMh8uY2VsZXN0LmFzdC52MS5SZXNvbHZlZFZhcmlhYmxlQgPgQQFS' + 'CXZhcmlhYmxlcxI8CgdzZWNyZXRzGAUgAygLMh0uY2VsZXN0LmFzdC52MS5SZXNvbHZlZFNlY3' + 'JldEID4EEBUgdzZWNyZXRzEjQKBGF1dGgYBiABKAsyGy5jZWxlc3QuYXN0LnYxLlJlc29sdmVk' + 'QXV0aEID4EEBUgRhdXRoElAKCWRhdGFiYXNlcxgHIAMoCzItLmNlbGVzdC5hc3QudjEuUmVzb2' + 'x2ZWRQcm9qZWN0LkRhdGFiYXNlc0VudHJ5QgPgQQFSCWRhdGFiYXNlcxJDCgpzZGtfY29uZmln' + 'GGMgASgLMh8uY2VsZXN0LmFzdC52MS5TZGtDb25maWd1cmF0aW9uQgPgQQJSCXNka0NvbmZpZx' + 'pTCglBcGlzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSMAoFdmFsdWUYAiABKAsyGi5jZWxlc3Qu' + 'YXN0LnYxLlJlc29sdmVkQXBpUgV2YWx1ZToCOAEaXQoORGF0YWJhc2VzRW50cnkSEAoDa2V5GA' + 'EgASgJUgNrZXkSNQoFdmFsdWUYAiABKAsyHy5jZWxlc3QuYXN0LnYxLlJlc29sdmVkRGF0YWJh' + 'c2VSBXZhbHVlOgI4AQ=='); @$core.Deprecated('Use resolvedApiDescriptor instead') const ResolvedApi$json = { @@ -178,44 +191,30 @@ const ResolvedFunction$json = { '1': 'ResolvedFunction', '2': [ {'1': 'function_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'functionId'}, - {'1': 'api_id', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'apiId'}, + {'1': 'parent_id', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'parentId'}, { - '1': 'http', + '1': 'http_config', '3': 3, '4': 1, '5': 11, '6': '.celest.ast.v1.ResolvedHttpConfig', '8': {}, - '10': 'http' + '10': 'httpConfig' }, { - '1': 'client_streaming', + '1': 'stream_config', '3': 4, '4': 1, - '5': 8, - '8': {}, - '10': 'clientStreaming' - }, - { - '1': 'server_streaming', - '3': 5, - '4': 1, - '5': 8, - '8': {}, - '10': 'serverStreaming' - }, - { - '1': 'environment_variables', - '3': 6, - '4': 3, - '5': 9, + '5': 11, + '6': '.celest.ast.v1.ResolvedStreamConfig', '8': {}, - '10': 'environmentVariables' + '10': 'streamConfig' }, - {'1': 'secrets', '3': 7, '4': 3, '5': 9, '8': {}, '10': 'secrets'}, + {'1': 'variables', '3': 5, '4': 3, '5': 9, '8': {}, '10': 'variables'}, + {'1': 'secrets', '3': 6, '4': 3, '5': 9, '8': {}, '10': 'secrets'}, { '1': 'policy_set', - '3': 8, + '3': 7, '4': 1, '5': 11, '6': '.cedar.v3.PolicySet', @@ -228,44 +227,52 @@ const ResolvedFunction$json = { /// Descriptor for `ResolvedFunction`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedFunctionDescriptor = $convert.base64Decode( 'ChBSZXNvbHZlZEZ1bmN0aW9uEiQKC2Z1bmN0aW9uX2lkGAEgASgJQgPgQQJSCmZ1bmN0aW9uSW' - 'QSGgoGYXBpX2lkGAIgASgJQgPgQQJSBWFwaUlkEjoKBGh0dHAYAyABKAsyIS5jZWxlc3QuYXN0' - 'LnYxLlJlc29sdmVkSHR0cENvbmZpZ0ID4EECUgRodHRwEi4KEGNsaWVudF9zdHJlYW1pbmcYBC' - 'ABKAhCA+BBAVIPY2xpZW50U3RyZWFtaW5nEi4KEHNlcnZlcl9zdHJlYW1pbmcYBSABKAhCA+BB' - 'AVIPc2VydmVyU3RyZWFtaW5nEjgKFWVudmlyb25tZW50X3ZhcmlhYmxlcxgGIAMoCUID4EEBUh' - 'RlbnZpcm9ubWVudFZhcmlhYmxlcxIdCgdzZWNyZXRzGAcgAygJQgPgQQFSB3NlY3JldHMSNwoK' - 'cG9saWN5X3NldBgIIAEoCzITLmNlZGFyLnYzLlBvbGljeVNldEID4EEBUglwb2xpY3lTZXQ='); + 'QSIAoJcGFyZW50X2lkGAIgASgJQgPgQQJSCHBhcmVudElkEkcKC2h0dHBfY29uZmlnGAMgASgL' + 'MiEuY2VsZXN0LmFzdC52MS5SZXNvbHZlZEh0dHBDb25maWdCA+BBAlIKaHR0cENvbmZpZxJNCg' + '1zdHJlYW1fY29uZmlnGAQgASgLMiMuY2VsZXN0LmFzdC52MS5SZXNvbHZlZFN0cmVhbUNvbmZp' + 'Z0ID4EECUgxzdHJlYW1Db25maWcSIQoJdmFyaWFibGVzGAUgAygJQgPgQQFSCXZhcmlhYmxlcx' + 'IdCgdzZWNyZXRzGAYgAygJQgPgQQFSB3NlY3JldHMSNwoKcG9saWN5X3NldBgHIAEoCzITLmNl' + 'ZGFyLnYzLlBvbGljeVNldEID4EEBUglwb2xpY3lTZXQ='); @$core.Deprecated('Use resolvedHttpConfigDescriptor instead') const ResolvedHttpConfig$json = { '1': 'ResolvedHttpConfig', '2': [ - {'1': 'method', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'method'}, + {'1': 'status', '3': 1, '4': 1, '5': 5, '8': {}, '10': 'status'}, { - '1': 'path', + '1': 'route', '3': 2, '4': 1, '5': 11, - '6': '.celest.ast.v1.HttpPath', + '6': '.celest.ast.v1.ResolvedHttpRoute', + '8': {}, + '10': 'route' + }, + { + '1': 'additional_routes', + '3': 3, + '4': 3, + '5': 11, + '6': '.celest.ast.v1.ResolvedHttpRoute', '8': {}, - '10': 'path' + '10': 'additionalRoutes' }, - {'1': 'status_code', '3': 3, '4': 1, '5': 5, '8': {}, '10': 'statusCode'}, { - '1': 'error_status', + '1': 'status_mappings', '3': 4, '4': 3, '5': 11, - '6': '.celest.ast.v1.ResolvedHttpConfig.ErrorStatusEntry', + '6': '.celest.ast.v1.ResolvedHttpConfig.StatusMappingsEntry', '8': {}, - '10': 'errorStatus' + '10': 'statusMappings' }, ], - '3': [ResolvedHttpConfig_ErrorStatusEntry$json], + '3': [ResolvedHttpConfig_StatusMappingsEntry$json], }; @$core.Deprecated('Use resolvedHttpConfigDescriptor instead') -const ResolvedHttpConfig_ErrorStatusEntry$json = { - '1': 'ErrorStatusEntry', +const ResolvedHttpConfig_StatusMappingsEntry$json = { + '1': 'StatusMappingsEntry', '2': [ {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, {'1': 'value', '3': 2, '4': 1, '5': 5, '10': 'value'}, @@ -275,84 +282,76 @@ const ResolvedHttpConfig_ErrorStatusEntry$json = { /// Descriptor for `ResolvedHttpConfig`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedHttpConfigDescriptor = $convert.base64Decode( - 'ChJSZXNvbHZlZEh0dHBDb25maWcSGwoGbWV0aG9kGAEgASgJQgPgQQJSBm1ldGhvZBIwCgRwYX' - 'RoGAIgASgLMhcuY2VsZXN0LmFzdC52MS5IdHRwUGF0aEID4EECUgRwYXRoEiQKC3N0YXR1c19j' - 'b2RlGAMgASgFQgPgQQJSCnN0YXR1c0NvZGUSWgoMZXJyb3Jfc3RhdHVzGAQgAygLMjIuY2VsZX' - 'N0LmFzdC52MS5SZXNvbHZlZEh0dHBDb25maWcuRXJyb3JTdGF0dXNFbnRyeUID4EECUgtlcnJv' - 'clN0YXR1cxo+ChBFcnJvclN0YXR1c0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGA' - 'IgASgFUgV2YWx1ZToCOAE='); - -@$core.Deprecated('Use httpPathDescriptor instead') -const HttpPath$json = { - '1': 'HttpPath', + 'ChJSZXNvbHZlZEh0dHBDb25maWcSGwoGc3RhdHVzGAEgASgFQgPgQQJSBnN0YXR1cxI7CgVyb3' + 'V0ZRgCIAEoCzIgLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWRIdHRwUm91dGVCA+BBAlIFcm91dGUS' + 'UgoRYWRkaXRpb25hbF9yb3V0ZXMYAyADKAsyIC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkSHR0cF' + 'JvdXRlQgPgQQFSEGFkZGl0aW9uYWxSb3V0ZXMSYwoPc3RhdHVzX21hcHBpbmdzGAQgAygLMjUu' + 'Y2VsZXN0LmFzdC52MS5SZXNvbHZlZEh0dHBDb25maWcuU3RhdHVzTWFwcGluZ3NFbnRyeUID4E' + 'ECUg5zdGF0dXNNYXBwaW5ncxpBChNTdGF0dXNNYXBwaW5nc0VudHJ5EhAKA2tleRgBIAEoCVID' + 'a2V5EhQKBXZhbHVlGAIgASgFUgV2YWx1ZToCOAE='); + +@$core.Deprecated('Use resolvedHttpRouteDescriptor instead') +const ResolvedHttpRoute$json = { + '1': 'ResolvedHttpRoute', '2': [ - {'1': 'path', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'path'}, - { - '1': 'parameters', - '3': 2, - '4': 3, - '5': 11, - '6': '.celest.ast.v1.HttpPath.ParametersEntry', - '8': {}, - '10': 'parameters' - }, + {'1': 'method', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'method'}, + {'1': 'path', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'path'}, ], - '3': [HttpPath_ParametersEntry$json], }; -@$core.Deprecated('Use httpPathDescriptor instead') -const HttpPath_ParametersEntry$json = { - '1': 'ParametersEntry', +/// Descriptor for `ResolvedHttpRoute`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedHttpRouteDescriptor = $convert.base64Decode( + 'ChFSZXNvbHZlZEh0dHBSb3V0ZRIbCgZtZXRob2QYASABKAlCA+BBAlIGbWV0aG9kEhcKBHBhdG' + 'gYAiABKAlCA+BBAlIEcGF0aA=='); + +@$core.Deprecated('Use resolvedStreamConfigDescriptor instead') +const ResolvedStreamConfig$json = { + '1': 'ResolvedStreamConfig', '2': [ - {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, { - '1': 'value', - '3': 2, + '1': 'type', + '3': 1, '4': 1, - '5': 11, - '6': '.celest.ast.v1.HttpParameter', - '10': 'value' + '5': 14, + '6': '.celest.ast.v1.ResolvedStreamConfig.Type', + '8': {}, + '10': 'type' }, ], - '7': {'7': true}, + '4': [ResolvedStreamConfig_Type$json], }; -/// Descriptor for `HttpPath`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List httpPathDescriptor = $convert.base64Decode( - 'CghIdHRwUGF0aBIXCgRwYXRoGAEgASgJQgPgQQJSBHBhdGgSTAoKcGFyYW1ldGVycxgCIAMoCz' - 'InLmNlbGVzdC5hc3QudjEuSHR0cFBhdGguUGFyYW1ldGVyc0VudHJ5QgPgQQFSCnBhcmFtZXRl' - 'cnMaWwoPUGFyYW1ldGVyc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EjIKBXZhbHVlGAIgASgLMh' - 'wuY2VsZXN0LmFzdC52MS5IdHRwUGFyYW1ldGVyUgV2YWx1ZToCOAE='); - -@$core.Deprecated('Use httpParameterDescriptor instead') -const HttpParameter$json = { - '1': 'HttpParameter', +@$core.Deprecated('Use resolvedStreamConfigDescriptor instead') +const ResolvedStreamConfig_Type$json = { + '1': 'Type', '2': [ - {'1': 'name', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'name'}, - {'1': 'type', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'type'}, - {'1': 'required', '3': 3, '4': 1, '5': 8, '8': {}, '10': 'required'}, + {'1': 'STREAM_CONFIG_TYPE_UNSPECIFIED', '2': 0}, + {'1': 'UNIDIRECTIONAL_CLIENT', '2': 1}, + {'1': 'UNIDIRECTIONAL_SERVER', '2': 2}, + {'1': 'BIDIRECTIONAL', '2': 3}, ], }; -/// Descriptor for `HttpParameter`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List httpParameterDescriptor = $convert.base64Decode( - 'Cg1IdHRwUGFyYW1ldGVyEhcKBG5hbWUYASABKAlCA+BBAlIEbmFtZRIXCgR0eXBlGAIgASgJQg' - 'PgQQJSBHR5cGUSHwoIcmVxdWlyZWQYAyABKAhCA+BBAlIIcmVxdWlyZWQ='); +/// Descriptor for `ResolvedStreamConfig`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedStreamConfigDescriptor = $convert.base64Decode( + 'ChRSZXNvbHZlZFN0cmVhbUNvbmZpZxJBCgR0eXBlGAEgASgOMiguY2VsZXN0LmFzdC52MS5SZX' + 'NvbHZlZFN0cmVhbUNvbmZpZy5UeXBlQgPgQQJSBHR5cGUicwoEVHlwZRIiCh5TVFJFQU1fQ09O' + 'RklHX1RZUEVfVU5TUEVDSUZJRUQQABIZChVVTklESVJFQ1RJT05BTF9DTElFTlQQARIZChVVTk' + 'lESVJFQ1RJT05BTF9TRVJWRVIQAhIRCg1CSURJUkVDVElPTkFMEAM='); -@$core.Deprecated('Use resolvedEnvironmentVariableDescriptor instead') -const ResolvedEnvironmentVariable$json = { - '1': 'ResolvedEnvironmentVariable', +@$core.Deprecated('Use resolvedVariableDescriptor instead') +const ResolvedVariable$json = { + '1': 'ResolvedVariable', '2': [ {'1': 'name', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'name'}, {'1': 'value', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'value'}, ], }; -/// Descriptor for `ResolvedEnvironmentVariable`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List resolvedEnvironmentVariableDescriptor = - $convert.base64Decode( - 'ChtSZXNvbHZlZEVudmlyb25tZW50VmFyaWFibGUSFwoEbmFtZRgBIAEoCUID4EECUgRuYW1lEh' - 'kKBXZhbHVlGAIgASgJQgPgQQJSBXZhbHVl'); +/// Descriptor for `ResolvedVariable`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedVariableDescriptor = $convert.base64Decode( + 'ChBSZXNvbHZlZFZhcmlhYmxlEhcKBG5hbWUYASABKAlCA+BBAlIEbmFtZRIZCgV2YWx1ZRgCIA' + 'EoCUID4EECUgV2YWx1ZQ=='); @$core.Deprecated('Use resolvedSecretDescriptor instead') const ResolvedSecret$json = { @@ -416,7 +415,14 @@ final $typed_data.Uint8List resolvedAuthDescriptor = $convert.base64Decode( const ResolvedAuthProvider$json = { '1': 'ResolvedAuthProvider', '2': [ - {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + { + '1': 'auth_provider_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'authProviderId' + }, { '1': 'type', '3': 2, @@ -493,17 +499,18 @@ const ResolvedAuthProvider_Type$json = { /// Descriptor for `ResolvedAuthProvider`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedAuthProviderDescriptor = $convert.base64Decode( - 'ChRSZXNvbHZlZEF1dGhQcm92aWRlchITCgJpZBgBIAEoCUID4EECUgJpZBJBCgR0eXBlGAIgAS' - 'gOMiguY2VsZXN0LmFzdC52MS5SZXNvbHZlZEF1dGhQcm92aWRlci5UeXBlQgPgQQJSBHR5cGUS' - 'TAoJZW1haWxfb3RwGAMgASgLMi0uY2VsZXN0LmFzdC52MS5SZXNvbHZlZEVtYWlsT3RwUHJvdm' - 'lkZXJDb25maWdIAFIIZW1haWxPdHASRgoHc21zX290cBgEIAEoCzIrLmNlbGVzdC5hc3QudjEu' - 'UmVzb2x2ZWRTbXNPdHBQcm92aWRlckNvbmZpZ0gAUgZzbXNPdHASSgoGZ29vZ2xlGAUgASgLMj' - 'AuY2VsZXN0LmFzdC52MS5SZXNvbHZlZEdvb2dsZU9BdXRoUHJvdmlkZXJDb25maWdIAFIGZ29v' - 'Z2xlEkoKBmdpdGh1YhgGIAEoCzIwLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWRHaXRIdWJPQXV0aF' - 'Byb3ZpZGVyQ29uZmlnSABSBmdpdGh1YhJHCgVhcHBsZRgHIAEoCzIvLmNlbGVzdC5hc3QudjEu' - 'UmVzb2x2ZWRBcHBsZU9BdXRoUHJvdmlkZXJDb25maWdIAFIFYXBwbGUiaQoEVHlwZRIiCh5BVV' - 'RIX1BST1ZJREVSX1RZUEVfVU5TUEVDSUZJRUQQABINCglFTUFJTF9PVFAQARILCgdTTVNfT1RQ' - 'EAISCgoGR09PR0xFEAMSCgoGR0lUSFVCEAQSCQoFQVBQTEUQBUIICgZjb25maWc='); + 'ChRSZXNvbHZlZEF1dGhQcm92aWRlchItChBhdXRoX3Byb3ZpZGVyX2lkGAEgASgJQgPgQQJSDm' + 'F1dGhQcm92aWRlcklkEkEKBHR5cGUYAiABKA4yKC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkQXV0' + 'aFByb3ZpZGVyLlR5cGVCA+BBAlIEdHlwZRJMCgllbWFpbF9vdHAYAyABKAsyLS5jZWxlc3QuYX' + 'N0LnYxLlJlc29sdmVkRW1haWxPdHBQcm92aWRlckNvbmZpZ0gAUghlbWFpbE90cBJGCgdzbXNf' + 'b3RwGAQgASgLMisuY2VsZXN0LmFzdC52MS5SZXNvbHZlZFNtc090cFByb3ZpZGVyQ29uZmlnSA' + 'BSBnNtc090cBJKCgZnb29nbGUYBSABKAsyMC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkR29vZ2xl' + 'T0F1dGhQcm92aWRlckNvbmZpZ0gAUgZnb29nbGUSSgoGZ2l0aHViGAYgASgLMjAuY2VsZXN0Lm' + 'FzdC52MS5SZXNvbHZlZEdpdEh1Yk9BdXRoUHJvdmlkZXJDb25maWdIAFIGZ2l0aHViEkcKBWFw' + 'cGxlGAcgASgLMi8uY2VsZXN0LmFzdC52MS5SZXNvbHZlZEFwcGxlT0F1dGhQcm92aWRlckNvbm' + 'ZpZ0gAUgVhcHBsZSJpCgRUeXBlEiIKHkFVVEhfUFJPVklERVJfVFlQRV9VTlNQRUNJRklFRBAA' + 'Eg0KCUVNQUlMX09UUBABEgsKB1NNU19PVFAQAhIKCgZHT09HTEUQAxIKCgZHSVRIVUIQBBIJCg' + 'VBUFBMRRAFQggKBmNvbmZpZw=='); @$core.Deprecated('Use resolvedEmailOtpProviderConfigDescriptor instead') const ResolvedEmailOtpProviderConfig$json = { @@ -645,7 +652,14 @@ final $typed_data.Uint8List resolvedAppleOAuthProviderConfigDescriptor = $conver const ResolvedExternalAuthProvider$json = { '1': 'ResolvedExternalAuthProvider', '2': [ - {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + { + '1': 'auth_provider_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'authProviderId' + }, { '1': 'type', '3': 2, @@ -692,14 +706,14 @@ const ResolvedExternalAuthProvider_Type$json = { /// Descriptor for `ResolvedExternalAuthProvider`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List resolvedExternalAuthProviderDescriptor = $convert.base64Decode( - 'ChxSZXNvbHZlZEV4dGVybmFsQXV0aFByb3ZpZGVyEhMKAmlkGAEgASgJQgPgQQJSAmlkEkkKBH' - 'R5cGUYAiABKA4yMC5jZWxlc3QuYXN0LnYxLlJlc29sdmVkRXh0ZXJuYWxBdXRoUHJvdmlkZXIu' - 'VHlwZUID4EECUgR0eXBlElcKCGZpcmViYXNlGAMgASgLMjkuY2VsZXN0LmFzdC52MS5SZXNvbH' - 'ZlZEZpcmViYXNlRXh0ZXJuYWxBdXRoUHJvdmlkZXJDb25maWdIAFIIZmlyZWJhc2USVwoIc3Vw' - 'YWJhc2UYBCABKAsyOS5jZWxlc3QuYXN0LnYxLlJlc29sdmVkU3VwYWJhc2VFeHRlcm5hbEF1dG' - 'hQcm92aWRlckNvbmZpZ0gAUghzdXBhYmFzZSJPCgRUeXBlEisKJ0VYVEVSTkFMX0FVVEhfUFJP' - 'VklERVJfVFlQRV9VTlNQRUNJRklFRBAAEgwKCEZJUkVCQVNFEAESDAoIU1VQQUJBU0UQAkIICg' - 'Zjb25maWc='); + 'ChxSZXNvbHZlZEV4dGVybmFsQXV0aFByb3ZpZGVyEi0KEGF1dGhfcHJvdmlkZXJfaWQYASABKA' + 'lCA+BBAlIOYXV0aFByb3ZpZGVySWQSSQoEdHlwZRgCIAEoDjIwLmNlbGVzdC5hc3QudjEuUmVz' + 'b2x2ZWRFeHRlcm5hbEF1dGhQcm92aWRlci5UeXBlQgPgQQJSBHR5cGUSVwoIZmlyZWJhc2UYAy' + 'ABKAsyOS5jZWxlc3QuYXN0LnYxLlJlc29sdmVkRmlyZWJhc2VFeHRlcm5hbEF1dGhQcm92aWRl' + 'ckNvbmZpZ0gAUghmaXJlYmFzZRJXCghzdXBhYmFzZRgEIAEoCzI5LmNlbGVzdC5hc3QudjEuUm' + 'Vzb2x2ZWRTdXBhYmFzZUV4dGVybmFsQXV0aFByb3ZpZGVyQ29uZmlnSABSCHN1cGFiYXNlIk8K' + 'BFR5cGUSKwonRVhURVJOQUxfQVVUSF9QUk9WSURFUl9UWVBFX1VOU1BFQ0lGSUVEEAASDAoIRk' + 'lSRUJBU0UQARIMCghTVVBBQkFTRRACQggKBmNvbmZpZw=='); @$core.Deprecated( 'Use resolvedFirebaseExternalAuthProviderConfigDescriptor instead') @@ -711,7 +725,7 @@ const ResolvedFirebaseExternalAuthProviderConfig$json = { '3': 1, '4': 1, '5': 11, - '6': '.celest.ast.v1.ResolvedEnvironmentVariable', + '6': '.celest.ast.v1.ResolvedVariable', '8': {}, '10': 'projectId' }, @@ -722,9 +736,9 @@ const ResolvedFirebaseExternalAuthProviderConfig$json = { final $typed_data.Uint8List resolvedFirebaseExternalAuthProviderConfigDescriptor = $convert.base64Decode( - 'CipSZXNvbHZlZEZpcmViYXNlRXh0ZXJuYWxBdXRoUHJvdmlkZXJDb25maWcSTgoKcHJvamVjdF' - '9pZBgBIAEoCzIqLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWRFbnZpcm9ubWVudFZhcmlhYmxlQgPg' - 'QQJSCXByb2plY3RJZA=='); + 'CipSZXNvbHZlZEZpcmViYXNlRXh0ZXJuYWxBdXRoUHJvdmlkZXJDb25maWcSQwoKcHJvamVjdF' + '9pZBgBIAEoCzIfLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWRWYXJpYWJsZUID4EECUglwcm9qZWN0' + 'SWQ='); @$core.Deprecated( 'Use resolvedSupabaseExternalAuthProviderConfigDescriptor instead') @@ -736,7 +750,7 @@ const ResolvedSupabaseExternalAuthProviderConfig$json = { '3': 1, '4': 1, '5': 11, - '6': '.celest.ast.v1.ResolvedEnvironmentVariable', + '6': '.celest.ast.v1.ResolvedVariable', '8': {}, '10': 'projectUrl' }, @@ -756,91 +770,241 @@ const ResolvedSupabaseExternalAuthProviderConfig$json = { final $typed_data.Uint8List resolvedSupabaseExternalAuthProviderConfigDescriptor = $convert.base64Decode( - 'CipSZXNvbHZlZFN1cGFiYXNlRXh0ZXJuYWxBdXRoUHJvdmlkZXJDb25maWcSUAoLcHJvamVjdF' - '91cmwYASABKAsyKi5jZWxlc3QuYXN0LnYxLlJlc29sdmVkRW52aXJvbm1lbnRWYXJpYWJsZUID' - '4EECUgpwcm9qZWN0VXJsEkEKCmp3dF9zZWNyZXQYAiABKAsyHS5jZWxlc3QuYXN0LnYxLlJlc2' - '9sdmVkU2VjcmV0QgPgQQFSCWp3dFNlY3JldA=='); - -@$core.Deprecated('Use sdkInfoDescriptor instead') -const SdkInfo$json = { - '1': 'SdkInfo', + 'CipSZXNvbHZlZFN1cGFiYXNlRXh0ZXJuYWxBdXRoUHJvdmlkZXJDb25maWcSRQoLcHJvamVjdF' + '91cmwYASABKAsyHy5jZWxlc3QuYXN0LnYxLlJlc29sdmVkVmFyaWFibGVCA+BBAlIKcHJvamVj' + 'dFVybBJBCgpqd3Rfc2VjcmV0GAIgASgLMh0uY2VsZXN0LmFzdC52MS5SZXNvbHZlZFNlY3JldE' + 'ID4EEBUglqd3RTZWNyZXQ='); + +@$core.Deprecated('Use resolvedDatabaseDescriptor instead') +const ResolvedDatabase$json = { + '1': 'ResolvedDatabase', '2': [ + {'1': 'database_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'databaseId'}, { '1': 'type', - '3': 1, + '3': 2, '4': 1, '5': 14, - '6': '.celest.ast.v1.SdkType', + '6': '.celest.ast.v1.ResolvedDatabase.Type', '8': {}, '10': 'type' }, { - '1': 'version', + '1': 'schema', + '3': 3, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedDatabaseSchema', + '8': {}, + '10': 'schema' + }, + { + '1': 'celest', + '3': 4, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedCelestDatabaseConfig', + '9': 0, + '10': 'celest' + }, + ], + '4': [ResolvedDatabase_Type$json], + '8': [ + {'1': 'config'}, + ], +}; + +@$core.Deprecated('Use resolvedDatabaseDescriptor instead') +const ResolvedDatabase_Type$json = { + '1': 'Type', + '2': [ + {'1': 'DATABASE_TYPE_UNSPECIFIED', '2': 0}, + {'1': 'CELEST', '2': 1}, + ], +}; + +/// Descriptor for `ResolvedDatabase`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedDatabaseDescriptor = $convert.base64Decode( + 'ChBSZXNvbHZlZERhdGFiYXNlEiQKC2RhdGFiYXNlX2lkGAEgASgJQgPgQQJSCmRhdGFiYXNlSW' + 'QSPQoEdHlwZRgCIAEoDjIkLmNlbGVzdC5hc3QudjEuUmVzb2x2ZWREYXRhYmFzZS5UeXBlQgPg' + 'QQJSBHR5cGUSQgoGc2NoZW1hGAMgASgLMiUuY2VsZXN0LmFzdC52MS5SZXNvbHZlZERhdGFiYX' + 'NlU2NoZW1hQgPgQQJSBnNjaGVtYRJFCgZjZWxlc3QYBCABKAsyKy5jZWxlc3QuYXN0LnYxLlJl' + 'c29sdmVkQ2VsZXN0RGF0YWJhc2VDb25maWdIAFIGY2VsZXN0IjEKBFR5cGUSHQoZREFUQUJBU0' + 'VfVFlQRV9VTlNQRUNJRklFRBAAEgoKBkNFTEVTVBABQggKBmNvbmZpZw=='); + +@$core.Deprecated('Use resolvedCelestDatabaseConfigDescriptor instead') +const ResolvedCelestDatabaseConfig$json = { + '1': 'ResolvedCelestDatabaseConfig', + '2': [ + { + '1': 'hostname', + '3': 1, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedVariable', + '8': {}, + '10': 'hostname' + }, + { + '1': 'token', '3': 2, '4': 1, '5': 11, - '6': '.celest.ast.v1.Version', + '6': '.celest.ast.v1.ResolvedSecret', '8': {}, - '10': 'version' + '10': 'token' }, + ], +}; + +/// Descriptor for `ResolvedCelestDatabaseConfig`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedCelestDatabaseConfigDescriptor = + $convert.base64Decode( + 'ChxSZXNvbHZlZENlbGVzdERhdGFiYXNlQ29uZmlnEkAKCGhvc3RuYW1lGAEgASgLMh8uY2VsZX' + 'N0LmFzdC52MS5SZXNvbHZlZFZhcmlhYmxlQgPgQQJSCGhvc3RuYW1lEjgKBXRva2VuGAIgASgL' + 'Mh0uY2VsZXN0LmFzdC52MS5SZXNvbHZlZFNlY3JldEID4EECUgV0b2tlbg=='); + +@$core.Deprecated('Use resolvedDatabaseSchemaDescriptor instead') +const ResolvedDatabaseSchema$json = { + '1': 'ResolvedDatabaseSchema', + '2': [ { - '1': 'enabled_experiments', - '3': 3, - '4': 3, + '1': 'database_schema_id', + '3': 1, + '4': 1, '5': 9, '8': {}, - '10': 'enabledExperiments' + '10': 'databaseSchemaId' + }, + { + '1': 'type', + '3': 2, + '4': 1, + '5': 14, + '6': '.celest.ast.v1.ResolvedDatabaseSchema.Type', + '8': {}, + '10': 'type' + }, + { + '1': 'drift', + '3': 3, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.ResolvedDriftDatabaseSchema', + '9': 0, + '10': 'drift' }, ], + '4': [ResolvedDatabaseSchema_Type$json], + '8': [ + {'1': 'schema'}, + ], }; -/// Descriptor for `SdkInfo`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List sdkInfoDescriptor = $convert.base64Decode( - 'CgdTZGtJbmZvEi8KBHR5cGUYASABKA4yFi5jZWxlc3QuYXN0LnYxLlNka1R5cGVCA+BBAlIEdH' - 'lwZRI1Cgd2ZXJzaW9uGAIgASgLMhYuY2VsZXN0LmFzdC52MS5WZXJzaW9uQgPgQQJSB3ZlcnNp' - 'b24SNAoTZW5hYmxlZF9leHBlcmltZW50cxgDIAMoCUID4EEBUhJlbmFibGVkRXhwZXJpbWVudH' - 'M='); +@$core.Deprecated('Use resolvedDatabaseSchemaDescriptor instead') +const ResolvedDatabaseSchema_Type$json = { + '1': 'Type', + '2': [ + {'1': 'DATABASE_SCHEMA_TYPE_UNSPECIFIED', '2': 0}, + {'1': 'DRIFT', '2': 1}, + ], +}; -@$core.Deprecated('Use versionDescriptor instead') -const Version$json = { - '1': 'Version', +/// Descriptor for `ResolvedDatabaseSchema`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedDatabaseSchemaDescriptor = $convert.base64Decode( + 'ChZSZXNvbHZlZERhdGFiYXNlU2NoZW1hEjEKEmRhdGFiYXNlX3NjaGVtYV9pZBgBIAEoCUID4E' + 'ECUhBkYXRhYmFzZVNjaGVtYUlkEkMKBHR5cGUYAiABKA4yKi5jZWxlc3QuYXN0LnYxLlJlc29s' + 'dmVkRGF0YWJhc2VTY2hlbWEuVHlwZUID4EECUgR0eXBlEkIKBWRyaWZ0GAMgASgLMiouY2VsZX' + 'N0LmFzdC52MS5SZXNvbHZlZERyaWZ0RGF0YWJhc2VTY2hlbWFIAFIFZHJpZnQiNwoEVHlwZRIk' + 'CiBEQVRBQkFTRV9TQ0hFTUFfVFlQRV9VTlNQRUNJRklFRBAAEgkKBURSSUZUEAFCCAoGc2NoZW' + '1h'); + +@$core.Deprecated('Use resolvedDriftDatabaseSchemaDescriptor instead') +const ResolvedDriftDatabaseSchema$json = { + '1': 'ResolvedDriftDatabaseSchema', '2': [ - {'1': 'major', '3': 1, '4': 1, '5': 5, '8': {}, '10': 'major'}, - {'1': 'minor', '3': 2, '4': 1, '5': 5, '8': {}, '10': 'minor'}, - {'1': 'patch', '3': 3, '4': 1, '5': 5, '8': {}, '10': 'patch'}, + {'1': 'version', '3': 1, '4': 1, '5': 5, '8': {}, '10': 'version'}, { - '1': 'pre_release', - '3': 4, - '4': 3, + '1': 'schema_json', + '3': 2, + '4': 1, '5': 11, - '6': '.google.protobuf.Value', + '6': '.google.protobuf.Struct', '8': {}, - '10': 'preRelease' + '10': 'schemaJson' }, + ], +}; + +/// Descriptor for `ResolvedDriftDatabaseSchema`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List resolvedDriftDatabaseSchemaDescriptor = + $convert.base64Decode( + 'ChtSZXNvbHZlZERyaWZ0RGF0YWJhc2VTY2hlbWESHQoHdmVyc2lvbhgBIAEoBUID4EECUgd2ZX' + 'JzaW9uEj0KC3NjaGVtYV9qc29uGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEID4EEC' + 'UgpzY2hlbWFKc29u'); + +@$core.Deprecated('Use sdkConfigurationDescriptor instead') +const SdkConfiguration$json = { + '1': 'SdkConfiguration', + '2': [ { - '1': 'build', - '3': 5, - '4': 3, + '1': 'celest', + '3': 1, + '4': 1, '5': 11, - '6': '.google.protobuf.Value', + '6': '.celest.ast.v1.Version', '8': {}, - '10': 'build' + '10': 'celest' }, { - '1': 'canonicalized_version', - '3': 6, + '1': 'dart', + '3': 2, '4': 1, - '5': 9, + '5': 11, + '6': '.celest.ast.v1.Sdk', + '8': {}, + '10': 'dart' + }, + { + '1': 'flutter', + '3': 3, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.Sdk', '8': {}, - '10': 'canonicalizedVersion' + '9': 0, + '10': 'flutter', + '17': true }, + { + '1': 'target_sdk', + '3': 4, + '4': 1, + '5': 14, + '6': '.celest.ast.v1.SdkType', + '8': {}, + '10': 'targetSdk' + }, + { + '1': 'feature_flags', + '3': 5, + '4': 3, + '5': 14, + '6': '.celest.ast.v1.FeatureFlag', + '8': {}, + '10': 'featureFlags' + }, + ], + '8': [ + {'1': '_flutter'}, ], }; -/// Descriptor for `Version`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List versionDescriptor = $convert.base64Decode( - 'CgdWZXJzaW9uEhkKBW1ham9yGAEgASgFQgPgQQJSBW1ham9yEhkKBW1pbm9yGAIgASgFQgPgQQ' - 'JSBW1pbm9yEhkKBXBhdGNoGAMgASgFQgPgQQJSBXBhdGNoEjwKC3ByZV9yZWxlYXNlGAQgAygL' - 'MhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlQgPgQQFSCnByZVJlbGVhc2USMQoFYnVpbGQYBSADKA' - 'syFi5nb29nbGUucHJvdG9idWYuVmFsdWVCA+BBAVIFYnVpbGQSOAoVY2Fub25pY2FsaXplZF92' - 'ZXJzaW9uGAYgASgJQgPgQQFSFGNhbm9uaWNhbGl6ZWRWZXJzaW9u'); +/// Descriptor for `SdkConfiguration`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sdkConfigurationDescriptor = $convert.base64Decode( + 'ChBTZGtDb25maWd1cmF0aW9uEjMKBmNlbGVzdBgBIAEoCzIWLmNlbGVzdC5hc3QudjEuVmVyc2' + 'lvbkID4EECUgZjZWxlc3QSKwoEZGFydBgCIAEoCzISLmNlbGVzdC5hc3QudjEuU2RrQgPgQQJS' + 'BGRhcnQSNgoHZmx1dHRlchgDIAEoCzISLmNlbGVzdC5hc3QudjEuU2RrQgPgQQFIAFIHZmx1dH' + 'RlcogBARI6Cgp0YXJnZXRfc2RrGAQgASgOMhYuY2VsZXN0LmFzdC52MS5TZGtUeXBlQgPgQQJS' + 'CXRhcmdldFNkaxJECg1mZWF0dXJlX2ZsYWdzGAUgAygOMhouY2VsZXN0LmFzdC52MS5GZWF0dX' + 'JlRmxhZ0ID4EEBUgxmZWF0dXJlRmxhZ3NCCgoIX2ZsdXR0ZXI='); diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pb.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pb.dart new file mode 100644 index 00000000..b9cb6bac --- /dev/null +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pb.dart @@ -0,0 +1,260 @@ +// +// Generated code. Do not modify. +// source: celest/ast/v1/sdks.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import '../../../google/protobuf/struct.pb.dart' as $22; +import 'sdks.pbenum.dart'; + +export 'sdks.pbenum.dart'; + +/// Information about an SDK used to deploy the project. +class Sdk extends $pb.GeneratedMessage { + factory Sdk({ + SdkType? type, + Version? version, + $core.Iterable<$core.String>? enabledExperiments, + }) { + final $result = create(); + if (type != null) { + $result.type = type; + } + if (version != null) { + $result.version = version; + } + if (enabledExperiments != null) { + $result.enabledExperiments.addAll(enabledExperiments); + } + return $result; + } + Sdk._() : super(); + factory Sdk.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory Sdk.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Sdk', + package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), + createEmptyInstance: create) + ..e(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, + defaultOrMaker: SdkType.SDK_TYPE_UNSPECIFIED, + valueOf: SdkType.valueOf, + enumValues: SdkType.values) + ..aOM(2, _omitFieldNames ? '' : 'version', + subBuilder: Version.create) + ..pPS(3, _omitFieldNames ? '' : 'enabledExperiments') + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Sdk clone() => Sdk()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Sdk copyWith(void Function(Sdk) updates) => + super.copyWith((message) => updates(message as Sdk)) as Sdk; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Sdk create() => Sdk._(); + Sdk createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Sdk getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Sdk? _defaultInstance; + + /// The type of the SDK. + @$pb.TagNumber(1) + SdkType get type => $_getN(0); + @$pb.TagNumber(1) + set type(SdkType v) { + setField(1, v); + } + + @$pb.TagNumber(1) + $core.bool hasType() => $_has(0); + @$pb.TagNumber(1) + void clearType() => clearField(1); + + /// The version of the SDK. + @$pb.TagNumber(2) + Version get version => $_getN(1); + @$pb.TagNumber(2) + set version(Version v) { + setField(2, v); + } + + @$pb.TagNumber(2) + $core.bool hasVersion() => $_has(1); + @$pb.TagNumber(2) + void clearVersion() => clearField(2); + @$pb.TagNumber(2) + Version ensureVersion() => $_ensure(1); + + /// The SDK experiments enabled for the project. + @$pb.TagNumber(3) + $core.List<$core.String> get enabledExperiments => $_getList(2); +} + +/// A semantic version, broken down by its components. +class Version extends $pb.GeneratedMessage { + factory Version({ + $core.int? major, + $core.int? minor, + $core.int? patch, + $core.Iterable<$22.Value>? preRelease, + $core.Iterable<$22.Value>? build, + $core.String? canonicalizedVersion, + }) { + final $result = create(); + if (major != null) { + $result.major = major; + } + if (minor != null) { + $result.minor = minor; + } + if (patch != null) { + $result.patch = patch; + } + if (preRelease != null) { + $result.preRelease.addAll(preRelease); + } + if (build != null) { + $result.build.addAll(build); + } + if (canonicalizedVersion != null) { + $result.canonicalizedVersion = canonicalizedVersion; + } + return $result; + } + Version._() : super(); + factory Version.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory Version.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Version', + package: const $pb.PackageName(_omitMessageNames ? '' : 'celest.ast.v1'), + createEmptyInstance: create) + ..a<$core.int>(1, _omitFieldNames ? '' : 'major', $pb.PbFieldType.O3) + ..a<$core.int>(2, _omitFieldNames ? '' : 'minor', $pb.PbFieldType.O3) + ..a<$core.int>(3, _omitFieldNames ? '' : 'patch', $pb.PbFieldType.O3) + ..pc<$22.Value>(4, _omitFieldNames ? '' : 'preRelease', $pb.PbFieldType.PM, + subBuilder: $22.Value.create) + ..pc<$22.Value>(5, _omitFieldNames ? '' : 'build', $pb.PbFieldType.PM, + subBuilder: $22.Value.create) + ..aOS(6, _omitFieldNames ? '' : 'canonicalizedVersion') + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Version clone() => Version()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Version copyWith(void Function(Version) updates) => + super.copyWith((message) => updates(message as Version)) as Version; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Version create() => Version._(); + Version createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Version getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Version? _defaultInstance; + + /// The major version number: "1" in "1.2.3". + @$pb.TagNumber(1) + $core.int get major => $_getIZ(0); + @$pb.TagNumber(1) + set major($core.int v) { + $_setSignedInt32(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasMajor() => $_has(0); + @$pb.TagNumber(1) + void clearMajor() => clearField(1); + + /// The minor version number: "2" in "1.2.3". + @$pb.TagNumber(2) + $core.int get minor => $_getIZ(1); + @$pb.TagNumber(2) + set minor($core.int v) { + $_setSignedInt32(1, v); + } + + @$pb.TagNumber(2) + $core.bool hasMinor() => $_has(1); + @$pb.TagNumber(2) + void clearMinor() => clearField(2); + + /// The patch version number: "3" in "1.2.3". + @$pb.TagNumber(3) + $core.int get patch => $_getIZ(2); + @$pb.TagNumber(3) + set patch($core.int v) { + $_setSignedInt32(2, v); + } + + @$pb.TagNumber(3) + $core.bool hasPatch() => $_has(2); + @$pb.TagNumber(3) + void clearPatch() => clearField(3); + + /// The pre-release identifier: "foo" in "1.2.3-foo". + /// + /// This is split into a list of components, each of which may be either a + /// string or a non-negative integer. It may also be empty, indicating that + /// this version has no pre-release identifier. + @$pb.TagNumber(4) + $core.List<$22.Value> get preRelease => $_getList(3); + + /// The build identifier: "foo" in "1.2.3+foo". + /// + /// This is split into a list of components, each of which may be either a + /// string or a non-negative integer. It may also be empty, indicating that + /// this version has no build identifier. + @$pb.TagNumber(5) + $core.List<$22.Value> get build => $_getList(4); + + /// The canonicalized version string. + @$pb.TagNumber(6) + $core.String get canonicalizedVersion => $_getSZ(5); + @$pb.TagNumber(6) + set canonicalizedVersion($core.String v) { + $_setString(5, v); + } + + @$pb.TagNumber(6) + $core.bool hasCanonicalizedVersion() => $_has(5); + @$pb.TagNumber(6) + void clearCanonicalizedVersion() => clearField(6); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbenum.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbenum.dart new file mode 100644 index 00000000..98d61613 --- /dev/null +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbenum.dart @@ -0,0 +1,36 @@ +// +// Generated code. Do not modify. +// source: celest/ast/v1/sdks.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +/// The type of SDK. +class SdkType extends $pb.ProtobufEnum { + static const SdkType SDK_TYPE_UNSPECIFIED = + SdkType._(0, _omitEnumNames ? '' : 'SDK_TYPE_UNSPECIFIED'); + static const SdkType DART = SdkType._(1, _omitEnumNames ? '' : 'DART'); + static const SdkType FLUTTER = SdkType._(2, _omitEnumNames ? '' : 'FLUTTER'); + + static const $core.List values = [ + SDK_TYPE_UNSPECIFIED, + DART, + FLUTTER, + ]; + + static final $core.Map<$core.int, SdkType> _byValue = + $pb.ProtobufEnum.initByValue(values); + static SdkType? valueOf($core.int value) => _byValue[value]; + + const SdkType._($core.int v, $core.String n) : super(v, n); +} + +const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbjson.dart b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbjson.dart new file mode 100644 index 00000000..53eb321c --- /dev/null +++ b/packages/celest_cloud/lib/src/proto/celest/ast/v1/sdks.pbjson.dart @@ -0,0 +1,112 @@ +// +// Generated code. Do not modify. +// source: celest/ast/v1/sdks.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use sdkTypeDescriptor instead') +const SdkType$json = { + '1': 'SdkType', + '2': [ + {'1': 'SDK_TYPE_UNSPECIFIED', '2': 0}, + {'1': 'DART', '2': 1}, + {'1': 'FLUTTER', '2': 2}, + ], +}; + +/// Descriptor for `SdkType`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List sdkTypeDescriptor = $convert.base64Decode( + 'CgdTZGtUeXBlEhgKFFNES19UWVBFX1VOU1BFQ0lGSUVEEAASCAoEREFSVBABEgsKB0ZMVVRURV' + 'IQAg=='); + +@$core.Deprecated('Use sdkDescriptor instead') +const Sdk$json = { + '1': 'Sdk', + '2': [ + { + '1': 'type', + '3': 1, + '4': 1, + '5': 14, + '6': '.celest.ast.v1.SdkType', + '8': {}, + '10': 'type' + }, + { + '1': 'version', + '3': 2, + '4': 1, + '5': 11, + '6': '.celest.ast.v1.Version', + '8': {}, + '10': 'version' + }, + { + '1': 'enabled_experiments', + '3': 3, + '4': 3, + '5': 9, + '8': {}, + '10': 'enabledExperiments' + }, + ], +}; + +/// Descriptor for `Sdk`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sdkDescriptor = $convert.base64Decode( + 'CgNTZGsSLwoEdHlwZRgBIAEoDjIWLmNlbGVzdC5hc3QudjEuU2RrVHlwZUID4EECUgR0eXBlEj' + 'UKB3ZlcnNpb24YAiABKAsyFi5jZWxlc3QuYXN0LnYxLlZlcnNpb25CA+BBAlIHdmVyc2lvbhI0' + 'ChNlbmFibGVkX2V4cGVyaW1lbnRzGAMgAygJQgPgQQFSEmVuYWJsZWRFeHBlcmltZW50cw=='); + +@$core.Deprecated('Use versionDescriptor instead') +const Version$json = { + '1': 'Version', + '2': [ + {'1': 'major', '3': 1, '4': 1, '5': 5, '8': {}, '10': 'major'}, + {'1': 'minor', '3': 2, '4': 1, '5': 5, '8': {}, '10': 'minor'}, + {'1': 'patch', '3': 3, '4': 1, '5': 5, '8': {}, '10': 'patch'}, + { + '1': 'pre_release', + '3': 4, + '4': 3, + '5': 11, + '6': '.google.protobuf.Value', + '8': {}, + '10': 'preRelease' + }, + { + '1': 'build', + '3': 5, + '4': 3, + '5': 11, + '6': '.google.protobuf.Value', + '8': {}, + '10': 'build' + }, + { + '1': 'canonicalized_version', + '3': 6, + '4': 1, + '5': 9, + '8': {}, + '10': 'canonicalizedVersion' + }, + ], +}; + +/// Descriptor for `Version`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List versionDescriptor = $convert.base64Decode( + 'CgdWZXJzaW9uEhkKBW1ham9yGAEgASgFQgPgQQJSBW1ham9yEhkKBW1pbm9yGAIgASgFQgPgQQ' + 'JSBW1pbm9yEhkKBXBhdGNoGAMgASgFQgPgQQJSBXBhdGNoEjwKC3ByZV9yZWxlYXNlGAQgAygL' + 'MhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlQgPgQQFSCnByZVJlbGVhc2USMQoFYnVpbGQYBSADKA' + 'syFi5nb29nbGUucHJvdG9idWYuVmFsdWVCA+BBAVIFYnVpbGQSOAoVY2Fub25pY2FsaXplZF92' + 'ZXJzaW9uGAYgASgJQgPgQQFSFGNhbm9uaWNhbGl6ZWRWZXJzaW9u'); diff --git a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pb.dart b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pb.dart index 0f63779b..082db8ee 100644 --- a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pb.dart +++ b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pb.dart @@ -11,7 +11,6 @@ import 'dart:core' as $core; -import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:protobuf/protobuf.dart' as $pb; import '../../../../google/protobuf/empty.pb.dart' as $1; @@ -89,7 +88,8 @@ class Session extends $pb.GeneratedMessage { ..aOM(6, _omitFieldNames ? '' : 'nextStep', subBuilder: AuthenticationStep.create) ..aOM(7, _omitFieldNames ? '' : 'client', - subBuilder: SessionClient.create); + subBuilder: SessionClient.create) + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -497,7 +497,9 @@ class StartSessionRequest extends $pb.GeneratedMessage { _StartSessionRequest_FactorByTag[$_whichOneof(0)]!; void clearFactor() => clearField($_whichOneof(0)); - /// Required. The parent resource in which the authentication session will be created. + /// Optional. The parent resource in which the authentication session will be created. + /// + /// If not provided, the session will be created in the root service context. /// /// Format: `organizations/{organization}` or `organizations/{organization}/projects/{project}`. @$pb.TagNumber(1) @@ -614,7 +616,8 @@ class AuthenticationStep extends $pb.GeneratedMessage { subBuilder: AuthenticationFactor.create) ..aOM( 7, _omitFieldNames ? '' : 'pendingConfirmation', - subBuilder: AuthenticationPendingConfirmation.create); + subBuilder: AuthenticationPendingConfirmation.create) + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -1125,7 +1128,8 @@ class ContinueSessionRequest extends $pb.GeneratedMessage { 4, _omitFieldNames ? '' : 'confirmation', subBuilder: AuthenticationPendingConfirmation.create) ..aOM(5, _omitFieldNames ? '' : 'resend', - subBuilder: AuthenticationFactor.create); + subBuilder: AuthenticationFactor.create) + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -1263,7 +1267,8 @@ class AuthenticationSuccess extends $pb.GeneratedMessage { createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'identityToken') ..aOM<$5.User>(2, _omitFieldNames ? '' : 'user', subBuilder: $5.User.create) - ..aOB(3, _omitFieldNames ? '' : 'isNewUser'); + ..aOB(3, _omitFieldNames ? '' : 'isNewUser') + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -1376,7 +1381,8 @@ class AuthenticationPendingConfirmation extends $pb.GeneratedMessage { ..aOM<$5.User>(1, _omitFieldNames ? '' : 'linkExistingUser', subBuilder: $5.User.create) ..aOM<$5.User>(2, _omitFieldNames ? '' : 'registerUser', - subBuilder: $5.User.create); + subBuilder: $5.User.create) + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -1674,7 +1680,7 @@ class OpenIdUserinfo extends $pb.GeneratedMessage { $core.String? phoneNumber, $core.bool? phoneNumberVerified, $22.Value? address, - $fixnum.Int64? updatedAt, + $core.int? updatedAt, }) { final $result = create(); if (sub != null) { @@ -1772,7 +1778,7 @@ class OpenIdUserinfo extends $pb.GeneratedMessage { ..aOB(18, _omitFieldNames ? '' : 'phone_number_verified') ..aOM<$22.Value>(19, _omitFieldNames ? '' : 'address', subBuilder: $22.Value.create) - ..aInt64(20, _omitFieldNames ? '' : 'updated_at') + ..a<$core.int>(20, _omitFieldNames ? '' : 'updated_at', $pb.PbFieldType.O3) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -2128,10 +2134,10 @@ class OpenIdUserinfo extends $pb.GeneratedMessage { /// in UTC until the date/time. /// (-- api-linter: core::0140::prepositions=disabled --) @$pb.TagNumber(20) - $fixnum.Int64 get updatedAt => $_getI64(19); + $core.int get updatedAt => $_getIZ(19); @$pb.TagNumber(20) - set updatedAt($fixnum.Int64 v) { - $_setInt64(19, v); + set updatedAt($core.int v) { + $_setSignedInt32(19, v); } @$pb.TagNumber(20) diff --git a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pbjson.dart b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pbjson.dart index d5dc4fe4..d213225f 100644 --- a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pbjson.dart +++ b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/authentication.pbjson.dart @@ -199,7 +199,7 @@ const StartSessionRequest$json = { /// Descriptor for `StartSessionRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List startSessionRequestDescriptor = $convert.base64Decode( - 'ChNTdGFydFNlc3Npb25SZXF1ZXN0EqsBCgZwYXJlbnQYASABKAlCkgHgQQK6SIsBugGHAQoMdm' + 'ChNTdGFydFNlc3Npb25SZXF1ZXN0EqsBCgZwYXJlbnQYASABKAlCkgHgQQG6SIsBugGHAQoMdm' 'FsaWRfcGFyZW50Eh1wYXJlbnQgbXVzdCBiZSBhIHZhbGlkIHBhcmVudBpYdGhpcyA9PSAnJyB8' 'fCB0aGlzLm1hdGNoZXMoJ15vcmdhbml6YXRpb25zL1teL10rJHxeb3JnYW5pemF0aW9ucy9bXi' '9dKy9wcm9qZWN0cy9bXi9dKyQnKVIGcGFyZW50ElcKCWVtYWlsX290cBgCIAEoCzI4LmNlbGVz' @@ -743,7 +743,7 @@ const OpenIdUserinfo$json = { '1': 'updated_at', '3': 20, '4': 1, - '5': 3, + '5': 5, '9': 18, '10': 'updated_at', '17': true @@ -787,7 +787,7 @@ final $typed_data.Uint8List openIdUserinfoDescriptor = $convert.base64Decode( 'cGhvbmVfbnVtYmVyGBEgASgJSA9SDHBob25lX251bWJlcogBARI5ChVwaG9uZV9udW1iZXJfdm' 'VyaWZpZWQYEiABKAhIEFIVcGhvbmVfbnVtYmVyX3ZlcmlmaWVkiAEBEjUKB2FkZHJlc3MYEyAB' 'KAsyFi5nb29nbGUucHJvdG9idWYuVmFsdWVIEVIHYWRkcmVzc4gBARIjCgp1cGRhdGVkX2F0GB' - 'QgASgDSBJSCnVwZGF0ZWRfYXSIAQFCBwoFX25hbWVCDQoLX2dpdmVuX25hbWVCDgoMX2ZhbWls' + 'QgASgFSBJSCnVwZGF0ZWRfYXSIAQFCBwoFX25hbWVCDQoLX2dpdmVuX25hbWVCDgoMX2ZhbWls' 'eV9uYW1lQg4KDF9taWRkbGVfbmFtZUILCglfbmlja25hbWVCFQoTX3ByZWZlcnJlZF91c2Vybm' 'FtZUIKCghfcHJvZmlsZUIKCghfcGljdHVyZUIKCghfd2Vic2l0ZUIICgZfZW1haWxCEQoPX2Vt' 'YWlsX3ZlcmlmaWVkQgkKB19nZW5kZXJCDAoKX2JpcnRoZGF0ZUILCglfem9uZWluZm9CCQoHX2' diff --git a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pb.dart b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pb.dart index f7302ec3..fc57e240 100644 --- a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pb.dart +++ b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pb.dart @@ -15,7 +15,6 @@ import 'package:protobuf/protobuf.dart' as $pb; import '../../../../google/protobuf/field_mask.pb.dart' as $46; import '../../../../google/protobuf/timestamp.pb.dart' as $20; -import '../../../../i18n/phonenumbers/phonenumber.pb.dart' as $52; import 'users.pbenum.dart'; export 'users.pbenum.dart'; @@ -32,10 +31,9 @@ class User extends $pb.GeneratedMessage { $core.String? familyName, $core.String? timeZone, $core.String? languageCode, - Email? email, - PhoneNumber? phoneNumber, + $core.Iterable? emails, + $core.Iterable? phoneNumbers, $core.Iterable? externalIdentities, - $core.String? company, }) { final $result = create(); if (name != null) { @@ -65,18 +63,15 @@ class User extends $pb.GeneratedMessage { if (languageCode != null) { $result.languageCode = languageCode; } - if (email != null) { - $result.email = email; + if (emails != null) { + $result.emails.addAll(emails); } - if (phoneNumber != null) { - $result.phoneNumber = phoneNumber; + if (phoneNumbers != null) { + $result.phoneNumbers.addAll(phoneNumbers); } if (externalIdentities != null) { $result.externalIdentities.addAll(externalIdentities); } - if (company != null) { - $result.company = company; - } return $result; } User._() : super(); @@ -103,13 +98,15 @@ class User extends $pb.GeneratedMessage { ..aOS(7, _omitFieldNames ? '' : 'familyName') ..aOS(8, _omitFieldNames ? '' : 'timeZone') ..aOS(9, _omitFieldNames ? '' : 'languageCode') - ..aOM(10, _omitFieldNames ? '' : 'email', subBuilder: Email.create) - ..aOM(11, _omitFieldNames ? '' : 'phoneNumber', + ..pc(10, _omitFieldNames ? '' : 'emails', $pb.PbFieldType.PM, + subBuilder: Email.create) + ..pc( + 11, _omitFieldNames ? '' : 'phoneNumbers', $pb.PbFieldType.PM, subBuilder: PhoneNumber.create) ..pc( 12, _omitFieldNames ? '' : 'externalIdentities', $pb.PbFieldType.PM, subBuilder: ExternalIdentity.create) - ..aOS(13, _omitFieldNames ? '' : 'company'); + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -263,68 +260,33 @@ class User extends $pb.GeneratedMessage { /// Optional. The user's email address. @$pb.TagNumber(10) - Email get email => $_getN(9); - @$pb.TagNumber(10) - set email(Email v) { - setField(10, v); - } - - @$pb.TagNumber(10) - $core.bool hasEmail() => $_has(9); - @$pb.TagNumber(10) - void clearEmail() => clearField(10); - @$pb.TagNumber(10) - Email ensureEmail() => $_ensure(9); + $core.List get emails => $_getList(9); /// Optional. The user's phone number. @$pb.TagNumber(11) - PhoneNumber get phoneNumber => $_getN(10); - @$pb.TagNumber(11) - set phoneNumber(PhoneNumber v) { - setField(11, v); - } - - @$pb.TagNumber(11) - $core.bool hasPhoneNumber() => $_has(10); - @$pb.TagNumber(11) - void clearPhoneNumber() => clearField(11); - @$pb.TagNumber(11) - PhoneNumber ensurePhoneNumber() => $_ensure(10); + $core.List get phoneNumbers => $_getList(10); /// Output only. The user's external identities. @$pb.TagNumber(12) $core.List get externalIdentities => $_getList(11); - - /// Optional. The user's company. - @$pb.TagNumber(13) - $core.String get company => $_getSZ(12); - @$pb.TagNumber(13) - set company($core.String v) { - $_setString(12, v); - } - - @$pb.TagNumber(13) - $core.bool hasCompany() => $_has(12); - @$pb.TagNumber(13) - void clearCompany() => clearField(13); } /// A email address used within Celest Cloud. class Email extends $pb.GeneratedMessage { factory Email({ $core.String? email, - $core.bool? verified, - $core.bool? primary, + $core.bool? isVerified, + $core.bool? isPrimary, }) { final $result = create(); if (email != null) { $result.email = email; } - if (verified != null) { - $result.verified = verified; + if (isVerified != null) { + $result.isVerified = isVerified; } - if (primary != null) { - $result.primary = primary; + if (isPrimary != null) { + $result.isPrimary = isPrimary; } return $result; } @@ -342,8 +304,8 @@ class Email extends $pb.GeneratedMessage { _omitMessageNames ? '' : 'celest.cloud.auth.v1alpha1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'email') - ..aOB(2, _omitFieldNames ? '' : 'verified') - ..aOB(3, _omitFieldNames ? '' : 'primary') + ..aOB(2, _omitFieldNames ? '' : 'isVerified') + ..aOB(3, _omitFieldNames ? '' : 'isPrimary') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -382,47 +344,47 @@ class Email extends $pb.GeneratedMessage { /// Whether the email address is verified. @$pb.TagNumber(2) - $core.bool get verified => $_getBF(1); + $core.bool get isVerified => $_getBF(1); @$pb.TagNumber(2) - set verified($core.bool v) { + set isVerified($core.bool v) { $_setBool(1, v); } @$pb.TagNumber(2) - $core.bool hasVerified() => $_has(1); + $core.bool hasIsVerified() => $_has(1); @$pb.TagNumber(2) - void clearVerified() => clearField(2); + void clearIsVerified() => clearField(2); /// Whether the email address is the primary email. @$pb.TagNumber(3) - $core.bool get primary => $_getBF(2); + $core.bool get isPrimary => $_getBF(2); @$pb.TagNumber(3) - set primary($core.bool v) { + set isPrimary($core.bool v) { $_setBool(2, v); } @$pb.TagNumber(3) - $core.bool hasPrimary() => $_has(2); + $core.bool hasIsPrimary() => $_has(2); @$pb.TagNumber(3) - void clearPrimary() => clearField(3); + void clearIsPrimary() => clearField(3); } /// A phone number used within Celest Cloud. class PhoneNumber extends $pb.GeneratedMessage { factory PhoneNumber({ - $52.PhoneNumber? phoneNumber, - $core.bool? verified, - $core.bool? primary, + $core.String? phoneNumber, + $core.bool? isVerified, + $core.bool? isPrimary, }) { final $result = create(); if (phoneNumber != null) { $result.phoneNumber = phoneNumber; } - if (verified != null) { - $result.verified = verified; + if (isVerified != null) { + $result.isVerified = isVerified; } - if (primary != null) { - $result.primary = primary; + if (isPrimary != null) { + $result.isPrimary = isPrimary; } return $result; } @@ -439,10 +401,10 @@ class PhoneNumber extends $pb.GeneratedMessage { package: const $pb.PackageName( _omitMessageNames ? '' : 'celest.cloud.auth.v1alpha1'), createEmptyInstance: create) - ..aOM<$52.PhoneNumber>(1, _omitFieldNames ? '' : 'phoneNumber', - subBuilder: $52.PhoneNumber.create) - ..aOB(2, _omitFieldNames ? '' : 'verified') - ..aOB(3, _omitFieldNames ? '' : 'primary'); + ..aOS(1, _omitFieldNames ? '' : 'phoneNumber') + ..aOB(2, _omitFieldNames ? '' : 'isVerified') + ..aOB(3, _omitFieldNames ? '' : 'isPrimary') + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -468,44 +430,42 @@ class PhoneNumber extends $pb.GeneratedMessage { /// Output only. The parsed and decoded phone number. @$pb.TagNumber(1) - $52.PhoneNumber get phoneNumber => $_getN(0); + $core.String get phoneNumber => $_getSZ(0); @$pb.TagNumber(1) - set phoneNumber($52.PhoneNumber v) { - setField(1, v); + set phoneNumber($core.String v) { + $_setString(0, v); } @$pb.TagNumber(1) $core.bool hasPhoneNumber() => $_has(0); @$pb.TagNumber(1) void clearPhoneNumber() => clearField(1); - @$pb.TagNumber(1) - $52.PhoneNumber ensurePhoneNumber() => $_ensure(0); /// Whether the phone number is verified. @$pb.TagNumber(2) - $core.bool get verified => $_getBF(1); + $core.bool get isVerified => $_getBF(1); @$pb.TagNumber(2) - set verified($core.bool v) { + set isVerified($core.bool v) { $_setBool(1, v); } @$pb.TagNumber(2) - $core.bool hasVerified() => $_has(1); + $core.bool hasIsVerified() => $_has(1); @$pb.TagNumber(2) - void clearVerified() => clearField(2); + void clearIsVerified() => clearField(2); /// Whether the phone number is the primary phone number. @$pb.TagNumber(3) - $core.bool get primary => $_getBF(2); + $core.bool get isPrimary => $_getBF(2); @$pb.TagNumber(3) - set primary($core.bool v) { + set isPrimary($core.bool v) { $_setBool(2, v); } @$pb.TagNumber(3) - $core.bool hasPrimary() => $_has(2); + $core.bool hasIsPrimary() => $_has(2); @$pb.TagNumber(3) - void clearPrimary() => clearField(3); + void clearIsPrimary() => clearField(3); } /// An external identity used within Celest Cloud. @@ -633,7 +593,8 @@ class CreateUserRequest extends $pb.GeneratedMessage { ..aOS(1, _omitFieldNames ? '' : 'parent') ..aOM(2, _omitFieldNames ? '' : 'user', subBuilder: User.create) ..aOS(3, _omitFieldNames ? '' : 'userId') - ..aOB(4, _omitFieldNames ? '' : 'validateOnly'); + ..aOB(4, _omitFieldNames ? '' : 'validateOnly') + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -764,7 +725,7 @@ class GetUserRequest extends $pb.GeneratedMessage { static GetUserRequest? _defaultInstance; /// The name of the user to retrieve. - /// Format: `me` | `users/{user}` | `organizations/{organization}/users/{user}` + /// Format: `users/{user}` | `organizations/{organization}/users/{user}` @$pb.TagNumber(1) $core.String get name => $_getSZ(0); @$pb.TagNumber(1) @@ -848,8 +809,12 @@ class ListUsersRequest extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(create); static ListUsersRequest? _defaultInstance; - /// The parent of the users to list. - /// Format: `organizations/{organization}` or `organizations/{organization}/projects/{project}` + /// Optional. The parent of the users to list. + /// + /// Format: `organizations/{organization}` or `organizations/{organization}/projects/{project}` + /// + /// If the parent is not provided, the users for the current context (as identified by the bearer token) + /// are listed. @$pb.TagNumber(1) $core.String get parent => $_getSZ(0); @$pb.TagNumber(1) @@ -945,7 +910,8 @@ class ListUsersResponse extends $pb.GeneratedMessage { createEmptyInstance: create) ..pc(1, _omitFieldNames ? '' : 'users', $pb.PbFieldType.PM, subBuilder: User.create) - ..aOS(2, _omitFieldNames ? '' : 'nextPageToken'); + ..aOS(2, _omitFieldNames ? '' : 'nextPageToken') + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' @@ -1023,7 +989,8 @@ class UpdateUserRequest extends $pb.GeneratedMessage { ..aOM(1, _omitFieldNames ? '' : 'user', subBuilder: User.create) ..aOM<$46.FieldMask>(2, _omitFieldNames ? '' : 'updateMask', subBuilder: $46.FieldMask.create) - ..aOB(3, _omitFieldNames ? '' : 'validateOnly'); + ..aOB(3, _omitFieldNames ? '' : 'validateOnly') + ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' diff --git a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pbjson.dart b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pbjson.dart index 052ea796..6f474b3c 100644 --- a/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pbjson.dart +++ b/packages/celest_cloud/lib/src/proto/celest/cloud/auth/v1alpha1/users.pbjson.dart @@ -95,26 +95,22 @@ const User$json = { '17': true }, { - '1': 'email', + '1': 'emails', '3': 10, - '4': 1, + '4': 3, '5': 11, '6': '.celest.cloud.auth.v1alpha1.Email', '8': {}, - '9': 4, - '10': 'email', - '17': true + '10': 'emails' }, { - '1': 'phone_number', + '1': 'phone_numbers', '3': 11, - '4': 1, + '4': 3, '5': 11, '6': '.celest.cloud.auth.v1alpha1.PhoneNumber', '8': {}, - '9': 5, - '10': 'phoneNumber', - '17': true + '10': 'phoneNumbers' }, { '1': 'external_identities', @@ -125,16 +121,6 @@ const User$json = { '8': {}, '10': 'externalIdentities' }, - { - '1': 'company', - '3': 13, - '4': 1, - '5': 9, - '8': {}, - '9': 6, - '10': 'company', - '17': true - }, ], '7': {}, '8': [ @@ -142,9 +128,6 @@ const User$json = { {'1': '_family_name'}, {'1': '_time_zone'}, {'1': '_language_code'}, - {'1': '_email'}, - {'1': '_phone_number'}, - {'1': '_company'}, ], }; @@ -157,57 +140,48 @@ final $typed_data.Uint8List userDescriptor = $convert.base64Decode( 'dWYuVGltZXN0YW1wQgPgQQNSCnVwZGF0ZVRpbWUSJwoKZ2l2ZW5fbmFtZRgGIAEoCUID4EEBSA' 'BSCWdpdmVuTmFtZYgBARIpCgtmYW1pbHlfbmFtZRgHIAEoCUID4EEBSAFSCmZhbWlseU5hbWWI' 'AQESJQoJdGltZV96b25lGAggASgJQgPgQQFIAlIIdGltZVpvbmWIAQESLQoNbGFuZ3VhZ2VfY2' - '9kZRgJIAEoCUID4EEBSANSDGxhbmd1YWdlQ29kZYgBARJBCgVlbWFpbBgKIAEoCzIhLmNlbGVz' - 'dC5jbG91ZC5hdXRoLnYxYWxwaGExLkVtYWlsQgPgQQFIBFIFZW1haWyIAQESVAoMcGhvbmVfbn' - 'VtYmVyGAsgASgLMicuY2VsZXN0LmNsb3VkLmF1dGgudjFhbHBoYTEuUGhvbmVOdW1iZXJCA+BB' - 'AUgFUgtwaG9uZU51bWJlcogBARJiChNleHRlcm5hbF9pZGVudGl0aWVzGAwgAygLMiwuY2VsZX' - 'N0LmNsb3VkLmF1dGgudjFhbHBoYTEuRXh0ZXJuYWxJZGVudGl0eUID4EEDUhJleHRlcm5hbElk' - 'ZW50aXRpZXMSIgoHY29tcGFueRgNIAEoCUID4EEBSAZSB2NvbXBhbnmIAQE6NepBMgoVY2xvdW' - 'QuY2VsZXN0LmRldi9Vc2VyEgx1c2Vycy97dXNlcn0qBXVzZXJzMgR1c2VyQg0KC19naXZlbl9u' - 'YW1lQg4KDF9mYW1pbHlfbmFtZUIMCgpfdGltZV96b25lQhAKDl9sYW5ndWFnZV9jb2RlQggKBl' - '9lbWFpbEIPCg1fcGhvbmVfbnVtYmVyQgoKCF9jb21wYW55'); + '9kZRgJIAEoCUID4EEBSANSDGxhbmd1YWdlQ29kZYgBARI+CgZlbWFpbHMYCiADKAsyIS5jZWxl' + 'c3QuY2xvdWQuYXV0aC52MWFscGhhMS5FbWFpbEID4EEBUgZlbWFpbHMSUQoNcGhvbmVfbnVtYm' + 'VycxgLIAMoCzInLmNlbGVzdC5jbG91ZC5hdXRoLnYxYWxwaGExLlBob25lTnVtYmVyQgPgQQFS' + 'DHBob25lTnVtYmVycxJiChNleHRlcm5hbF9pZGVudGl0aWVzGAwgAygLMiwuY2VsZXN0LmNsb3' + 'VkLmF1dGgudjFhbHBoYTEuRXh0ZXJuYWxJZGVudGl0eUID4EEDUhJleHRlcm5hbElkZW50aXRp' + 'ZXM6NepBMgoVY2xvdWQuY2VsZXN0LmRldi9Vc2VyEgx1c2Vycy97dXNlcn0qBXVzZXJzMgR1c2' + 'VyQg0KC19naXZlbl9uYW1lQg4KDF9mYW1pbHlfbmFtZUIMCgpfdGltZV96b25lQhAKDl9sYW5n' + 'dWFnZV9jb2Rl'); @$core.Deprecated('Use emailDescriptor instead') const Email$json = { '1': 'Email', '2': [ {'1': 'email', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'email'}, - {'1': 'verified', '3': 2, '4': 1, '5': 8, '8': {}, '10': 'verified'}, - {'1': 'primary', '3': 3, '4': 1, '5': 8, '8': {}, '10': 'primary'}, + {'1': 'is_verified', '3': 2, '4': 1, '5': 8, '8': {}, '10': 'isVerified'}, + {'1': 'is_primary', '3': 3, '4': 1, '5': 8, '8': {}, '10': 'isPrimary'}, ], }; /// Descriptor for `Email`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List emailDescriptor = $convert.base64Decode( 'CgVFbWFpbBJZCgVlbWFpbBgBIAEoCUJD4EECukg9ugE6Cgt2YWxpZF9lbWFpbBIbZW1haWwgbX' - 'VzdCBiZSBhIHZhbGlkIGVtYWlsGg50aGlzLmlzRW1haWwoKVIFZW1haWwSHwoIdmVyaWZpZWQY' - 'AiABKAhCA+BBA1IIdmVyaWZpZWQSHQoHcHJpbWFyeRgDIAEoCEID4EEDUgdwcmltYXJ5'); + 'VzdCBiZSBhIHZhbGlkIGVtYWlsGg50aGlzLmlzRW1haWwoKVIFZW1haWwSJAoLaXNfdmVyaWZp' + 'ZWQYAiABKAhCA+BBA1IKaXNWZXJpZmllZBIiCgppc19wcmltYXJ5GAMgASgIQgPgQQNSCWlzUH' + 'JpbWFyeQ=='); @$core.Deprecated('Use phoneNumberDescriptor instead') const PhoneNumber$json = { '1': 'PhoneNumber', '2': [ - { - '1': 'phone_number', - '3': 1, - '4': 1, - '5': 11, - '6': '.i18n.phonenumbers.PhoneNumber', - '8': {}, - '10': 'phoneNumber' - }, - {'1': 'verified', '3': 2, '4': 1, '5': 8, '8': {}, '10': 'verified'}, - {'1': 'primary', '3': 3, '4': 1, '5': 8, '8': {}, '10': 'primary'}, + {'1': 'phone_number', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'phoneNumber'}, + {'1': 'is_verified', '3': 2, '4': 1, '5': 8, '8': {}, '10': 'isVerified'}, + {'1': 'is_primary', '3': 3, '4': 1, '5': 8, '8': {}, '10': 'isPrimary'}, ], }; /// Descriptor for `PhoneNumber`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List phoneNumberDescriptor = $convert.base64Decode( - 'CgtQaG9uZU51bWJlchKqAQoMcGhvbmVfbnVtYmVyGAEgASgLMh4uaTE4bi5waG9uZW51bWJlcn' - 'MuUGhvbmVOdW1iZXJCZ+BBArpIYboBXgoSdmFsaWRfcGhvbmVfbnVtYmVyEihwaG9uZV9udW1i' - 'ZXIgbXVzdCBpbmNsdWRlIGEgY291bnRyeSBjb2RlGh50aGlzLnJhd19pbnB1dC5zdGFydHNXaX' - 'RoKCcrJylSC3Bob25lTnVtYmVyEh8KCHZlcmlmaWVkGAIgASgIQgPgQQNSCHZlcmlmaWVkEh0K' - 'B3ByaW1hcnkYAyABKAhCA+BBA1IHcHJpbWFyeQ=='); + 'CgtQaG9uZU51bWJlchKAAQoMcGhvbmVfbnVtYmVyGAEgASgJQl3gQQK6SFe6AVQKEnZhbGlkX3' + 'Bob25lX251bWJlchIocGhvbmVfbnVtYmVyIG11c3QgaW5jbHVkZSBhIGNvdW50cnkgY29kZRoU' + 'dGhpcy5zdGFydHNXaXRoKCcrJylSC3Bob25lTnVtYmVyEiQKC2lzX3ZlcmlmaWVkGAIgASgIQg' + 'PgQQNSCmlzVmVyaWZpZWQSIgoKaXNfcHJpbWFyeRgDIAEoCEID4EEDUglpc1ByaW1hcnk='); @$core.Deprecated('Use externalIdentityDescriptor instead') const ExternalIdentity$json = { @@ -293,13 +267,13 @@ const ListUsersRequest$json = { /// Descriptor for `ListUsersRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List listUsersRequestDescriptor = $convert.base64Decode( - 'ChBMaXN0VXNlcnNSZXF1ZXN0ErUBCgZwYXJlbnQYASABKAlCnAHgQQL6QRcSFWNsb3VkLmNlbG' - 'VzdC5kZXYvVXNlcrpIfLoBeQoMdmFsaWRfcGFyZW50Eh1wYXJlbnQgbXVzdCBiZSBhIHZhbGlk' + 'ChBMaXN0VXNlcnNSZXF1ZXN0ErgBCgZwYXJlbnQYASABKAlCnwHgQQL6QRcSFWNsb3VkLmNlbG' + 'VzdC5kZXYvVXNlcrpIf7oBeQoMdmFsaWRfcGFyZW50Eh1wYXJlbnQgbXVzdCBiZSBhIHZhbGlk' 'IHBhcmVudBpKdGhpcy5tYXRjaGVzKCdeb3JnYW5pemF0aW9ucy9bXi9dKyR8Xm9yZ2FuaXphdG' - 'lvbnMvW14vXSsvcHJvamVjdHMvW14vXSskJylSBnBhcmVudBInCglwYWdlX3NpemUYAiABKAVC' - 'CuBBAbpIBBoCKABSCHBhZ2VTaXplEiIKCnBhZ2VfdG9rZW4YAyABKAlCA+BBAVIJcGFnZVRva2' - 'VuEhsKBmZpbHRlchgEIAEoCUID4EEBUgZmaWx0ZXISHgoIb3JkZXJfYnkYBSABKAlCA+BBAVIH' - 'b3JkZXJCeQ=='); + 'lvbnMvW14vXSsvcHJvamVjdHMvW14vXSskJynQAQFSBnBhcmVudBInCglwYWdlX3NpemUYAiAB' + 'KAVCCuBBAbpIBBoCKABSCHBhZ2VTaXplEiIKCnBhZ2VfdG9rZW4YAyABKAlCA+BBAVIJcGFnZV' + 'Rva2VuEhsKBmZpbHRlchgEIAEoCUID4EEBUgZmaWx0ZXISHgoIb3JkZXJfYnkYBSABKAlCA+BB' + 'AVIHb3JkZXJCeQ=='); @$core.Deprecated('Use listUsersResponseDescriptor instead') const ListUsersResponse$json = { @@ -362,12 +336,11 @@ final $typed_data.Uint8List updateUserRequestDescriptor = $convert.base64Decode( 'ChFVcGRhdGVVc2VyUmVxdWVzdBI5CgR1c2VyGAEgASgLMiAuY2VsZXN0LmNsb3VkLmF1dGgudj' 'FhbHBoYTEuVXNlckID4EECUgR1c2VyEkAKC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnBy' 'b3RvYnVmLkZpZWxkTWFza0ID4EEBUgp1cGRhdGVNYXNrEigKDXZhbGlkYXRlX29ubHkYAyABKA' - 'hCA+BBAVIMdmFsaWRhdGVPbmx5OpQCukiQAhqNAgoQdmFsaWRfZmllbGRfbWFzaxJwdXBkYXRl' - 'X21hc2sgbWF5IG9ubHkgaW5jbHVkZTogJ2dpdmVuX25hbWUnLCAnZmFtaWx5X25hbWUnLCAnZW' - '1haWwnLCAncGhvbmVfbnVtYmVyJywgJ3RpbWVfem9uZScsICdsYW5ndWFnZV9jb2RlJxqGAXRo' - 'aXMudXBkYXRlX21hc2sucGF0aHMuYWxsKHBhdGgsIHBhdGggaW4gWyduYW1lJywgJ2dpdmVuX2' - '5hbWUnLCAnZmFtaWx5X25hbWUnLCAnZW1haWwnLCAncGhvbmVfbnVtYmVyJywgJ3RpbWVfem9u' - 'ZScsICdsYW5ndWFnZV9jb2RlJ10p'); + 'hCA+BBAVIMdmFsaWRhdGVPbmx5OuEBukjdARraAQoQdmFsaWRfZmllbGRfbWFzaxJXdXBkYXRl' + 'X21hc2sgbWF5IG9ubHkgaW5jbHVkZTogJ2dpdmVuX25hbWUnLCAnZmFtaWx5X25hbWUnLCAndG' + 'ltZV96b25lJywgJ2xhbmd1YWdlX2NvZGUnGm10aGlzLnVwZGF0ZV9tYXNrLnBhdGhzLmFsbChw' + 'YXRoLCBwYXRoIGluIFsnbmFtZScsICdnaXZlbl9uYW1lJywgJ2ZhbWlseV9uYW1lJywgJ3RpbW' + 'Vfem9uZScsICdsYW5ndWFnZV9jb2RlJ10p'); @$core.Deprecated('Use deleteUserRequestDescriptor instead') const DeleteUserRequest$json = { diff --git a/packages/celest_core/lib/src/auth/user.dart b/packages/celest_core/lib/src/auth/user.dart index 339b1480..527e2278 100644 --- a/packages/celest_core/lib/src/auth/user.dart +++ b/packages/celest_core/lib/src/auth/user.dart @@ -1,3 +1,8 @@ +import 'package:cedar/src/model/value.dart' show EntityUid; +import 'package:collection/collection.dart' + show IterableExtension, ListExtensions; +import 'package:meta/meta.dart'; + final class AuthenticatedUser { const AuthenticatedUser({ required this.cork, @@ -25,36 +30,278 @@ final class AuthenticatedUser { } } +@immutable final class User { const User({ required this.userId, - this.displayName, - required this.email, - bool? emailVerified, - }) : emailVerified = emailVerified ?? false; + this.createTime, + this.updateTime, + this.givenName, + this.familyName, + this.timeZone, + this.languageCode, + List emails = const [], + List phoneNumbers = const [], + this.roles = const [EntityUid.of('Celest::Role', 'authenticated')], + this.etag, + }) : emails = emails as Emails, + phoneNumbers = phoneNumbers as PhoneNumbers; factory User.fromJson(Map json) { return User( - userId: json['sub'] as String, - displayName: json['name'] as String?, - email: json['email'] as String, - emailVerified: json['email_verified'] as bool?, + userId: json['userId'] as String, + createTime: DateTime.parse(json['createTime'] as String), + updateTime: json['updateTime'] == null + ? null + : DateTime.parse(json['updateTime'] as String), + givenName: json['givenName'] as String?, + familyName: json['familyName'] as String?, + timeZone: json['timeZone'] as String?, + languageCode: json['languageCode'] as String?, + emails: (json['emails'] as List?) + ?.map((e) => Email.fromJson(e as Map)) + .toList() ?? + const [], + phoneNumbers: (json['phoneNumbers'] as List?) + ?.map((pn) => PhoneNumber.fromJson(pn as Map)) + .toList() ?? + const [], + roles: (json['roles'] as List?) + ?.cast>() + .map(EntityUid.fromJson) + .toList() ?? + const [], + etag: json['etag'] as String?, ); } + String get id => userId; + final String userId; - final String? displayName; - final String? email; - final bool emailVerified; + final DateTime? createTime; + final DateTime? updateTime; + + final String? givenName; + final String? familyName; + final String? timeZone; + final String? languageCode; + + final Emails emails; + Email? get primaryEmail => emails.primary; + + final PhoneNumbers phoneNumbers; + PhoneNumber? get primaryPhoneNumber => phoneNumbers.primary; + + final List roles; + final String? etag; Map toJson() => { - 'sub': userId, - if (displayName != null) 'name': displayName, - if (email != null) 'email': email, - if (email != null) 'email_verified': emailVerified, + 'userId': userId, + if (createTime case final createTime?) + 'createTime': createTime.toIso8601String(), + if (updateTime case final updateTime?) + 'updateTime': updateTime.toIso8601String(), + if (givenName != null) 'givenName': givenName, + if (familyName != null) 'familyName': familyName, + if (timeZone != null) 'timeZone': timeZone, + if (languageCode != null) 'languageCode': languageCode, + if (emails.isNotEmpty) 'emails': emails.map((e) => e.toJson()).toList(), + if (phoneNumbers.isNotEmpty) + 'phoneNumbers': phoneNumbers.map((pn) => pn.toJson()).toList(), + if (roles.isNotEmpty) + 'roles': roles.map((uid) => uid.normalized.toJson()).toList(), + if (etag != null) 'etag': etag, }; + User copyWith({ + DateTime? createTime, + DateTime? updateTime, + String? givenName, + String? familyName, + String? timeZone, + String? languageCode, + List? emails, + List? phoneNumbers, + List? roles, + String? etag, + }) { + return User( + userId: id, + createTime: createTime ?? this.createTime, + updateTime: updateTime ?? this.updateTime, + givenName: givenName ?? this.givenName, + familyName: familyName ?? this.familyName, + timeZone: timeZone ?? this.timeZone, + languageCode: languageCode ?? this.languageCode, + emails: emails ?? this.emails, + phoneNumbers: phoneNumbers ?? this.phoneNumbers, + roles: roles ?? this.roles, + etag: etag ?? this.etag, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is User && + userId == other.userId && + createTime?.toUtc() == other.createTime?.toUtc() && + updateTime?.toUtc() == other.updateTime?.toUtc() && + givenName == other.givenName && + familyName == other.familyName && + emails.equals(other.emails) && + phoneNumbers.equals(other.phoneNumbers) && + timeZone == other.timeZone && + languageCode == other.languageCode && + roles.equals(other.roles) && + etag == other.etag; + } + @override - String toString() => 'User(userId: $userId, displayName: $displayName, ' - 'email: $email, emailVerified: $emailVerified)'; + int get hashCode => Object.hash( + userId, + createTime, + updateTime, + givenName, + familyName, + Object.hashAllUnordered(emails), + Object.hashAllUnordered(phoneNumbers), + timeZone, + languageCode, + Object.hashAllUnordered(roles), + etag, + ); + + @override + String toString() { + final buf = StringBuffer('User(id: $userId'); + if (givenName != null) { + buf.write(', givenName: $givenName'); + } + if (familyName != null) { + buf.write(', familyName: $familyName'); + } + if (emails.isNotEmpty) { + buf.write(', emails: $emails'); + } + if (phoneNumbers.isNotEmpty) { + buf.write(', phoneNumbers: $phoneNumbers'); + } + if (timeZone != null) { + buf.write(', timeZone: $timeZone'); + } + if (languageCode != null) { + buf.write(', languageCode: $languageCode'); + } + if (createTime != null) { + buf.write(', createTime: $createTime'); + } + if (updateTime != null) { + buf.write(', updateTime: $updateTime'); + } + if (roles.isNotEmpty) { + buf.write(', roles: $roles'); + } + if (etag != null) { + buf.write(', etag: $etag'); + } + buf.write(')'); + return buf.toString(); + } +} + +@immutable +final class Email { + const Email({ + required this.email, + this.isVerified = false, + this.isPrimary = false, + }); + + factory Email.fromJson(Map json) { + return Email( + email: json['email'] as String, + isVerified: json['isVerified'] as bool? ?? false, + isPrimary: json['isPrimary'] as bool? ?? false, + ); + } + + final String email; + final bool isVerified; + final bool isPrimary; + + Map toJson() => { + 'email': email, + 'isVerified': isVerified, + 'isPrimary': isPrimary, + }; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is Email && + runtimeType == other.runtimeType && + email == other.email && + isVerified == other.isVerified && + isPrimary == other.isPrimary; + } + + @override + int get hashCode => Object.hash(email, isVerified, isPrimary); + + @override + String toString() => email; +} + +extension type const Emails(List _) implements List { + Email? get primary => firstWhereOrNull((e) => e.isPrimary) ?? firstOrNull; +} + +@immutable +final class PhoneNumber { + const PhoneNumber({ + required this.phoneNumber, + this.isVerified = false, + this.isPrimary = false, + }); + + factory PhoneNumber.fromJson(Map json) { + return PhoneNumber( + phoneNumber: json['phoneNumber'] as String, + isVerified: json['isVerified'] as bool? ?? false, + isPrimary: json['isPrimary'] as bool? ?? false, + ); + } + + final String phoneNumber; + final bool isVerified; + final bool isPrimary; + + Map toJson() => { + 'phoneNumber': phoneNumber, + 'isVerified': isVerified, + 'isPrimary': isPrimary, + }; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is PhoneNumber && + runtimeType == other.runtimeType && + phoneNumber == other.phoneNumber && + isVerified == other.isVerified && + isPrimary == other.isPrimary; + } + + @override + int get hashCode => Object.hash(phoneNumber, isVerified, isPrimary); + + @override + String toString() => phoneNumber; +} + +extension type const PhoneNumbers(List _) + implements List { + PhoneNumber? get primary => + firstWhereOrNull((pn) => pn.isPrimary) ?? firstOrNull; } diff --git a/packages/celest_core/lib/src/util/json.dart b/packages/celest_core/lib/src/util/json.dart index 8b20d018..9a8b355e 100644 --- a/packages/celest_core/lib/src/util/json.dart +++ b/packages/celest_core/lib/src/util/json.dart @@ -9,7 +9,8 @@ extension JsonUtf8 on Object { static final encoder = JsonUtf8Encoder(); /// A JSON decoder that decodes from UTF-8. - static final decoder = utf8.decoder.fuse(json.decoder); + static final decoder = + utf8.decoder.fuse(json.decoder).cast(); /// Encodes a JSON [object] to a UTF-8 buffer. static Uint8List encode(Object? object) { @@ -18,7 +19,12 @@ extension JsonUtf8 on Object { /// Decodes a UTF-8 buffer to a JSON object. static Object? decode(List bytes) { - return decoder.convert(bytes); + return decoder.convert(bytes as Uint8List); + } + + /// Decodes a [Stream] of UTF-8 bytes to a JSON object. + static Future decodeStream(Stream> stream) async { + return stream.transform(decoder).first; } static Never _invalidJson(Object? json) { diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index dfcced26..68d01412 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: async: ^2.11.0 + cedar: ^0.2.2 collection: ^1.18.0 grpc: ^4.0.0 http: ^1.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index e6eeb295..6860a608 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,8 @@ name: celest_dev publish_to: none environment: - sdk: ^3.3.0 - flutter: ">=3.19.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dev_dependencies: melos: ^5.3.0 diff --git a/services/celest_cloud_auth/.dockerignore b/services/celest_cloud_auth/.dockerignore new file mode 100644 index 00000000..21504f8f --- /dev/null +++ b/services/celest_cloud_auth/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +Dockerfile +build/ +.dart_tool/ +.git/ +.github/ +.gitignore +.idea/ +.packages diff --git a/services/celest_cloud_auth/.gitignore b/services/celest_cloud_auth/.gitignore new file mode 100644 index 00000000..3a857904 --- /dev/null +++ b/services/celest_cloud_auth/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/services/celest_cloud_auth/CHANGELOG.md b/services/celest_cloud_auth/CHANGELOG.md new file mode 100644 index 00000000..951a5a3f --- /dev/null +++ b/services/celest_cloud_auth/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial release. diff --git a/services/celest_cloud_auth/Dockerfile b/services/celest_cloud_auth/Dockerfile new file mode 100644 index 00000000..c333dee7 --- /dev/null +++ b/services/celest_cloud_auth/Dockerfile @@ -0,0 +1,21 @@ +# Use latest stable channel SDK. +FROM dart:stable AS build + +# Resolve app dependencies. +WORKDIR /app +COPY pubspec.* ./ +RUN dart pub get + +# Copy app source code (except anything in .dockerignore) and AOT compile app. +COPY . . +RUN dart compile exe bin/server.dart -o bin/server + +# Build minimal serving image from AOT-compiled `/server` +# and the pre-built AOT-runtime in the `/runtime/` directory of the base image. +FROM scratch +COPY --from=build /runtime/ / +COPY --from=build /app/bin/server /app/bin/ + +# Start server. +EXPOSE 8080 +CMD ["/app/bin/server"] diff --git a/services/celest_cloud_auth/Makefile b/services/celest_cloud_auth/Makefile new file mode 100644 index 00000000..0fa17296 --- /dev/null +++ b/services/celest_cloud_auth/Makefile @@ -0,0 +1,21 @@ +lib/src/database/schema/*.drift.dart: lib/src/database/schema/*.drift + @echo "Generating Drift code..." + @dart run build_runner build --delete-conflicting-outputs + +drift_schema/*.json: lib/src/database/schema/*.drift.dart + @echo "Generating schema JSON..." + @mkdir -p drift_schema + @dart run drift_dev schema dump lib/src/database/auth_database.dart drift_schema/ + +lib/src/database/schema_versions.dart: drift_schema/*.json + @echo "Generating migration steps..." + @dart run drift_dev schema steps drift_schema/ lib/src/database/schema_versions.dart + +drift: lib/src/database/schema_versions.dart + +cedar: lib/src/authorization/cedar/* + @echo "Generating Cedar code..." + @dart run tool/generate_policy_set.dart + +.PHONY: drift cedar +all: drift cedar diff --git a/services/celest_cloud_auth/README.md b/services/celest_cloud_auth/README.md new file mode 100644 index 00000000..e695d9de --- /dev/null +++ b/services/celest_cloud_auth/README.md @@ -0,0 +1,49 @@ +A server app built using [Shelf](https://pub.dev/packages/shelf), +configured to enable running with [Docker](https://www.docker.com/). + +This sample code handles HTTP GET requests to `/` and `/echo/` + +# Running the sample + +## Running with the Dart SDK + +You can run the example with the [Dart SDK](https://dart.dev/get-dart) +like this: + +``` +$ dart run bin/server.dart +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +## Running with Docker + +If you have [Docker Desktop](https://www.docker.com/get-started) installed, you +can build and run with the `docker` command: + +``` +$ docker build . -t myserver +$ docker run -it -p 8080:8080 myserver +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +You should see the logging printed in the first terminal: +``` +2021-05-06T15:47:04.620417 0:00:00.000158 GET [200] / +2021-05-06T15:47:08.392928 0:00:00.001216 GET [200] /echo/I_love_Dart +``` diff --git a/services/celest_cloud_auth/analysis_options.yaml b/services/celest_cloud_auth/analysis_options.yaml new file mode 100644 index 00000000..80f9f7c6 --- /dev/null +++ b/services/celest_cloud_auth/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:celest_lints/app.yaml + +analyzer: + errors: + # Print statements are trapped by parent zone and sent to logger. + avoid_print: ignore + implementation_imports: ignore + exclude: + - '**/*.g.dart' + - '**/*.drift.dart' diff --git a/services/celest_cloud_auth/bin/server.dart b/services/celest_cloud_auth/bin/server.dart new file mode 100644 index 00000000..87e8f26f --- /dev/null +++ b/services/celest_cloud_auth/bin/server.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_router/shelf_router.dart'; + +// Configure routes. +final _router = Router() + ..get('/', _rootHandler) + ..get('/echo/', _echoHandler); + +Response _rootHandler(Request req) { + return Response.ok('Hello, World!\n'); +} + +Response _echoHandler(Request request) { + final message = request.params['message']; + return Response.ok('$message\n'); +} + +void main(List args) async { + // Use any available host or container IP (usually `0.0.0.0`). + final ip = InternetAddress.anyIPv4; + + // Configure a pipeline that logs requests. + final handler = + Pipeline().addMiddleware(logRequests()).addHandler(_router.call); + + // For running in containers, we respect the PORT environment variable. + final port = int.parse(Platform.environment['PORT'] ?? '8080'); + final server = await serve(handler, ip, port); + print('Server listening on port ${server.port}'); +} diff --git a/services/celest_cloud_auth/build.yaml b/services/celest_cloud_auth/build.yaml new file mode 100644 index 00000000..86c92f3c --- /dev/null +++ b/services/celest_cloud_auth/build.yaml @@ -0,0 +1,34 @@ +targets: + drift: + auto_apply_builders: false + builders: + drift_dev: + enabled: false + + drift_dev:analyzer: + enabled: true + options: &options + named_parameters: true + store_date_time_values_as_text: false + + sql: + dialect: sqlite + options: + version: "3.38" + modules: + - json1 + + # Reduces generated code + skip_verification_code: true + data_class_to_companions: false + + drift_dev:modular: + enabled: true + options: *options + + $default: + dependencies: + - ":drift" + builders: + drift_dev: + enabled: false diff --git a/services/celest_cloud_auth/drift_schema/drift_schema_v1.json b/services/celest_cloud_auth/drift_schema/drift_schema_v1.json new file mode 100644 index 00000000..36a45e2f --- /dev/null +++ b/services/celest_cloud_auth/drift_schema/drift_schema_v1.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"users","was_declared_in_moor":true,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":"NOT NULL UNIQUE","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"create_time","getter_name":"createTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]},{"name":"update_time","getter_name":"updateTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"trigger","data":{"on":0,"references_in_body":[0],"name":"users_update_time","sql":"CREATE TRIGGER IF NOT EXISTS users_update_time\nAFTER UPDATE ON users\nBEGIN\n UPDATE users\n SET update_time = unixepoch('now', 'subsec')\n WHERE id = OLD.id;\nEND;"}},{"id":2,"references":[],"type":"table","data":{"name":"cedar_types","was_declared_in_moor":true,"columns":[{"name":"fqn","getter_name":"fqn","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY ON CONFLICT IGNORE","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"namespace","getter_name":"namespace","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (rtrim(fqn, '::' || type)) VIRTUAL","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (text_split(fqn, '::', -1)) VIRTUAL","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[2],"type":"table","data":{"name":"cedar_entities","was_declared_in_moor":true,"columns":[{"name":"entity_type","getter_name":"entityType","moor_type":"string","nullable":false,"customConstraints":"NOT NULL REFERENCES cedar_types(fqn)","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"entity_id","getter_name":"entityId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"attribute_json","getter_name":"attributeJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL DEFAULT '{}'","default_dart":"const CustomExpression('\\'{}\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarAttributesConverter()","dart_type_name":"Map"}},{"name":"entity_json","getter_name":"entityJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (json_object('type', entity_type, 'id', entity_id)) VIRTUAL","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"],"type_converter":{"dart_expr":"const CedarEntityUidConverter()","dart_type_name":"EntityUid"}}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT cedar_entities_pk PRIMARY KEY(entity_type, entity_id)ON CONFLICT IGNORE"],"explicit_pk":["entity_type","entity_id"]}},{"id":4,"references":[0,3],"type":"trigger","data":{"on":0,"references_in_body":[0,3],"name":"users_create","sql":"CREATE TRIGGER IF NOT EXISTS users_create\nBEFORE INSERT ON users\nBEGIN\n INSERT INTO cedar_entities(entity_type, entity_id)\n VALUES ('Celest::User', NEW.id);\nEND;"}},{"id":5,"references":[3],"type":"table","data":{"name":"cedar_relationships","was_declared_in_moor":true,"columns":[{"name":"entity_type","getter_name":"entityType","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entity_id","getter_name":"entityId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entity_json","getter_name":"entityJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (json_object('type', entity_type, 'id', entity_id)) VIRTUAL","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"],"type_converter":{"dart_expr":"const CedarEntityUidConverter()","dart_type_name":"EntityUid"}},{"name":"parent_type","getter_name":"parentType","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"parent_id","getter_name":"parentId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"parent_json","getter_name":"parentJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (json_object('type', parent_type, 'id', parent_id)) VIRTUAL","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"],"type_converter":{"dart_expr":"const CedarEntityUidConverter()","dart_type_name":"EntityUid"}}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT cedar_relationships_pk PRIMARY KEY(entity_type, entity_id, parent_type, parent_id)ON CONFLICT IGNORE","CONSTRAINT cedar_relationships_fk_entity FOREIGN KEY(entity_type, entity_id)REFERENCES cedar_entities(entity_type, entity_id)","CONSTRAINT cedar_relationships_fk_parent FOREIGN KEY(parent_type, parent_id)REFERENCES cedar_entities(entity_type, entity_id)"],"explicit_pk":["entity_type","entity_id","parent_type","parent_id"]}},{"id":6,"references":[0,5,3],"type":"trigger","data":{"on":0,"references_in_body":[0,5,3],"name":"users_delete","sql":"CREATE TRIGGER IF NOT EXISTS users_delete\nAFTER DELETE ON users\nBEGIN\n DELETE FROM cedar_relationships\n WHERE \n entity_id = OLD.id \n AND entity_type = 'Celest::User';\n DELETE FROM cedar_entities\n WHERE\n entity_id = OLD.id\n AND entity_type = 'Celest::User';\nEND;"}},{"id":7,"references":[0],"type":"table","data":{"name":"user_metadata","was_declared_in_moor":true,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"given_name","getter_name":"givenName","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"family_name","getter_name":"familyName","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"language_code","getter_name":"languageCode","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT user_metadata_user_fk FOREIGN KEY(user_id)REFERENCES users(id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"]}},{"id":8,"references":[7],"type":"index","data":{"on":7,"name":"user_metadata_user_id_idx","sql":"CREATE INDEX IF NOT EXISTS user_metadata_user_id_idx ON user_metadata(user_id);","unique":false,"columns":[]}},{"id":9,"references":[0],"type":"table","data":{"name":"user_emails","was_declared_in_moor":true,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"verified","getter_name":"verified","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL DEFAULT FALSE","default_dart":"const CustomExpression('FALSE')","default_client_dart":null,"dsl_features":[]},{"name":"is_primary","getter_name":"isPrimary","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL DEFAULT FALSE","default_dart":"const CustomExpression('FALSE')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT user_emails_pk PRIMARY KEY(user_id, email)","CONSTRAINT user_emails_user_fk FOREIGN KEY(user_id)REFERENCES users(id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"],"explicit_pk":["user_id","email"]}},{"id":10,"references":[0],"type":"table","data":{"name":"user_phone_numbers","was_declared_in_moor":true,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"phone_number","getter_name":"phoneNumber","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"phone_number_data","getter_name":"phoneNumberData","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const PhoneNumberConverter()","dart_type_name":"PhoneNumber"}},{"name":"verified","getter_name":"verified","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL DEFAULT FALSE","default_dart":"const CustomExpression('FALSE')","default_client_dart":null,"dsl_features":[]},{"name":"is_primary","getter_name":"isPrimary","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL DEFAULT FALSE","default_dart":"const CustomExpression('FALSE')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT user_phone_numbers_pk PRIMARY KEY(user_id, phone_number)","CONSTRAINT user_phone_numbers_user_fk FOREIGN KEY(user_id)REFERENCES users(id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"],"explicit_pk":["user_id","phone_number"]}},{"id":11,"references":[0,7,9,10],"type":"view","data":{"name":"users_view","sql":"CREATE VIEW IF NOT EXISTS users_view AS SELECT users.*, metadata.given_name, metadata.family_name, metadata.time_zone, metadata.language_code, (SELECT json_group_array(json_object('email', email, 'verified', verified, 'primary', is_primary)) FROM user_emails WHERE user_id = users.id) AS emails, (SELECT json_group_array(json_object('phone_number', phone_number, 'phone_number_data', phone_number_data, 'verified', verified, 'primary', is_primary)) FROM user_phone_numbers WHERE user_id = users.id) AS phone_numbers FROM users LEFT JOIN user_metadata AS metadata ON users.id = metadata.user_id;","dart_info_name":"UsersView","columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"create_time","getter_name":"createTime","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"update_time","getter_name":"updateTime","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"given_name","getter_name":"givenName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"family_name","getter_name":"familyName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"language_code","getter_name":"languageCode","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"emails","getter_name":"emails","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EmailsConverter()","dart_type_name":"List"}},{"name":"phone_numbers","getter_name":"phoneNumbers","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const PhoneNumbersConverter()","dart_type_name":"List"}}]}},{"id":12,"references":[11,0,7,9,10],"type":"trigger","data":{"on":11,"references_in_body":[11,0,7,9,10],"name":"users_view_insert","sql":"CREATE TRIGGER IF NOT EXISTS users_view_insert\nINSTEAD OF INSERT ON users_view\nBEGIN\n INSERT INTO users (id)\n VALUES (NEW.id);\n INSERT INTO user_metadata (user_id, given_name, family_name, time_zone, language_code)\n VALUES (NEW.id, NEW.given_name, NEW.family_name, NEW.time_zone, NEW.language_code);\n INSERT INTO user_emails (user_id, email, verified, is_primary)\n SELECT NEW.id, json_extract(value, '$.email'), coalesce(json_extract(value, '$.verified'), 0), coalesce(json_extract(value, '$.primary'), 0)\n FROM json_each(NEW.emails);\n INSERT INTO user_phone_numbers (user_id, phone_number, phone_number_data, verified, is_primary)\n SELECT NEW.id, json_extract(value, '$.phoneNumber'), json_extract(value, '$.phoneNumberData'), coalesce(json_extract(value, '$.verified'), 0), coalesce(json_extract(value, '$.primary'), 0)\n FROM json_each(NEW.phone_numbers);\nEND;"}},{"id":13,"references":[11,7,9],"type":"trigger","data":{"on":11,"references_in_body":[11,7,9],"name":"users_view_update","sql":"CREATE TRIGGER IF NOT EXISTS users_view_update\nINSTEAD OF UPDATE ON users_view\nBEGIN\n UPDATE user_metadata\n SET given_name = NEW.given_name, family_name = NEW.family_name, time_zone = NEW.time_zone, language_code = NEW.language_code\n WHERE user_id = OLD.id;\n -- merge the old and new emails\n REPLACE INTO user_emails (user_id, email, verified, is_primary)\n SELECT OLD.id, json_extract(value, '$.email'), json_extract(value, '$.verified'), json_extract(value, '$.primary')\n FROM json_each(NEW.emails);\nEND;"}},{"id":14,"references":[11,0,7,9,10],"type":"trigger","data":{"on":11,"references_in_body":[11,0,7,9,10],"name":"users_view_delete","sql":"CREATE TRIGGER IF NOT EXISTS users_view_delete\nINSTEAD OF DELETE ON users_view\nBEGIN\n DELETE FROM users\n WHERE id = OLD.id;\n DELETE FROM user_metadata\n WHERE user_id = OLD.id;\n DELETE FROM user_emails\n WHERE user_id = OLD.id;\n DELETE FROM user_phone_numbers\n WHERE user_id = OLD.id;\nEND;"}},{"id":15,"references":[],"type":"table","data":{"name":"celest_projects","was_declared_in_moor":true,"columns":[{"name":"project_id","getter_name":"projectId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_ast","getter_name":"resolvedAst","moor_type":"blob","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ResolvedProjectConverter()","dart_type_name":"ResolvedProject"}},{"name":"etag","getter_name":"etag","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (hex(md5(resolved_ast))) STORED","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":16,"references":[15],"type":"table","data":{"name":"celest_apis","was_declared_in_moor":true,"columns":[{"name":"api_id","getter_name":"apiId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"project_id","getter_name":"projectId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_ast","getter_name":"resolvedAst","moor_type":"blob","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ResolvedApiConverter()","dart_type_name":"ResolvedApi"}},{"name":"etag","getter_name":"etag","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (hex(md5(resolved_ast))) STORED","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":false,"constraints":["CONSTRAINT celest_apis_project_fk FOREIGN KEY(project_id)REFERENCES celest_projects(project_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"]}},{"id":17,"references":[16],"type":"index","data":{"on":16,"name":"celest_apis_project_idx","sql":"CREATE INDEX IF NOT EXISTS celest_apis_project_idx ON celest_apis(project_id);","unique":false,"columns":[]}},{"id":18,"references":[16,3],"type":"trigger","data":{"on":16,"references_in_body":[16,3],"name":"celest_apis_trigger_create","sql":"CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_create\nBEFORE INSERT ON celest_apis\nBEGIN\n INSERT INTO cedar_entities(entity_type, entity_id)\n VALUES ('Celest::Api', NEW.api_id);\nEND;"}},{"id":19,"references":[16,5,3],"type":"trigger","data":{"on":16,"references_in_body":[16,5,3],"name":"celest_apis_trigger_delete","sql":"CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_delete\nAFTER DELETE ON celest_apis\nBEGIN\n DELETE FROM cedar_relationships\n WHERE \n entity_type = 'Celest::Api'\n AND entity_id = OLD.api_id;\n DELETE FROM cedar_relationships\n WHERE \n parent_type = 'Celest::Api'\n AND parent_id = OLD.api_id;\n DELETE FROM cedar_entities\n WHERE\n entity_type = 'Celest::Api'\n AND entity_id = OLD.api_id;\nEND;"}},{"id":20,"references":[16],"type":"table","data":{"name":"celest_functions","was_declared_in_moor":true,"columns":[{"name":"function_id","getter_name":"functionId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"api_id","getter_name":"apiId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_ast","getter_name":"resolvedAst","moor_type":"blob","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ResolvedFunctionConverter()","dart_type_name":"ResolvedCloudFunction"}},{"name":"etag","getter_name":"etag","moor_type":"string","nullable":false,"customConstraints":"NOT NULL GENERATED ALWAYS AS (hex(md5(resolved_ast))) STORED","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":false,"constraints":["CONSTRAINT celest_functions_api_fk FOREIGN KEY(api_id)REFERENCES celest_apis(api_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"]}},{"id":21,"references":[20],"type":"index","data":{"on":20,"name":"celest_functions_api_idx","sql":"CREATE INDEX IF NOT EXISTS celest_functions_api_idx ON celest_functions(api_id);","unique":false,"columns":[]}},{"id":22,"references":[20,3,5],"type":"trigger","data":{"on":20,"references_in_body":[20,3,5],"name":"celest_functions_trigger_create","sql":"CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_create\nBEFORE INSERT ON celest_functions\nBEGIN\n INSERT INTO cedar_entities(entity_type, entity_id)\n VALUES ('Celest::Function', NEW.function_id);\n INSERT INTO cedar_relationships(entity_type, entity_id, parent_type, parent_id)\n VALUES ('Celest::Function', NEW.function_id, 'Celest::Api', NEW.api_id);\nEND;"}},{"id":23,"references":[20,5,3],"type":"trigger","data":{"on":20,"references_in_body":[20,5,3],"name":"celest_functions_trigger_delete","sql":"CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_delete\nAFTER DELETE ON celest_functions\nBEGIN\n DELETE FROM cedar_relationships\n WHERE \n entity_type = 'Celest::Function'\n AND entity_id = OLD.function_id;\n DELETE FROM cedar_relationships\n WHERE \n parent_type = 'Celest::Function'\n AND parent_id = OLD.function_id;\n DELETE FROM cedar_entities\n WHERE\n entity_type = 'Celest::Function'\n AND entity_id = OLD.function_id;\nEND;"}},{"id":24,"references":[5],"type":"index","data":{"on":5,"name":"cedar_relationships_fk_entity_idx","sql":"CREATE INDEX IF NOT EXISTS cedar_relationships_fk_entity_idx ON cedar_relationships(entity_type, entity_id);","unique":false,"columns":[]}},{"id":25,"references":[5],"type":"index","data":{"on":5,"name":"cedar_relationships_fk_parent_idx","sql":"CREATE INDEX IF NOT EXISTS cedar_relationships_fk_parent_idx ON cedar_relationships(parent_type, parent_id);","unique":false,"columns":[]}},{"id":26,"references":[],"type":"table","data":{"name":"cedar_policies","was_declared_in_moor":true,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"policy_id","getter_name":"policyId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL UNIQUE","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"policy","getter_name":"policy","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarPolicyConverter()","dart_type_name":"Policy"}},{"name":"enforcement_level","getter_name":"enforcementLevel","moor_type":"int","nullable":false,"customConstraints":"NOT NULL DEFAULT 1","default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["CHECK(enforcement_level IN (0, 1))"]}},{"id":27,"references":[],"type":"table","data":{"name":"cedar_policy_templates","was_declared_in_moor":true,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"template_id","getter_name":"templateId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL UNIQUE","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"template","getter_name":"template","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarPolicyConverter()","dart_type_name":"Policy"}}],"is_virtual":false,"without_rowid":false,"constraints":["CHECK(template IS NOT NULL OR template IS NOT NULL)"]}},{"id":28,"references":[27,3],"type":"table","data":{"name":"cedar_policy_template_links","was_declared_in_moor":true,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"policy_id","getter_name":"policyId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL UNIQUE","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"template_id","getter_name":"templateId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"principal_type","getter_name":"principalType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"principal_id","getter_name":"principalId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resource_type","getter_name":"resourceType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resource_id","getter_name":"resourceId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"enforcement_level","getter_name":"enforcementLevel","moor_type":"int","nullable":false,"customConstraints":"NOT NULL DEFAULT 1","default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["CHECK(principal_type IS NOT NULL AND principal_id IS NOT NULL OR resource_type IS NOT NULL AND resource_id IS NOT NULL)","CHECK(enforcement_level IN (0, 1))","CONSTRAINT cedar_policy_template_links_fk_template_id FOREIGN KEY(template_id)REFERENCES cedar_policy_templates(template_id)ON UPDATE CASCADE ON DELETE CASCADE","CONSTRAINT cedar_policy_template_links_fk_principal FOREIGN KEY(principal_type, principal_id)REFERENCES cedar_entities(entity_type, entity_id)ON DELETE CASCADE","CONSTRAINT cedar_policy_template_links_fk_resource FOREIGN KEY(resource_type, resource_id)REFERENCES cedar_entities(entity_type, entity_id)ON DELETE CASCADE"]}},{"id":29,"references":[28],"type":"index","data":{"on":28,"name":"cedar_policy_template_links_fk_template_id_idx","sql":"CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_template_id_idx ON cedar_policy_template_links(template_id);","unique":false,"columns":[]}},{"id":30,"references":[28],"type":"index","data":{"on":28,"name":"cedar_policy_template_links_fk_principal_idx","sql":"CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_principal_idx ON cedar_policy_template_links(principal_type, principal_id);","unique":false,"columns":[]}},{"id":31,"references":[28],"type":"index","data":{"on":28,"name":"cedar_policy_template_links_fk_resource_idx","sql":"CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_resource_idx ON cedar_policy_template_links(resource_type, resource_id);","unique":false,"columns":[]}},{"id":32,"references":[],"type":"table","data":{"name":"cedar_authorization_logs","was_declared_in_moor":true,"columns":[{"name":"rowid","getter_name":"rowid","moor_type":"int","nullable":false,"customConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"create_time","getter_name":"createTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]},{"name":"expire_time","getter_name":"expireTime","moor_type":"dateTime","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"principal_type","getter_name":"principalType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"principal_id","getter_name":"principalId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"action_type","getter_name":"actionType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"action_id","getter_name":"actionId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resource_type","getter_name":"resourceType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resource_id","getter_name":"resourceId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"context_json","getter_name":"contextJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL DEFAULT '{}'","default_dart":"const CustomExpression('\\'{}\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarAttributesConverter()","dart_type_name":"Map"}},{"name":"decision","getter_name":"decision","moor_type":"bool","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"reasons_json","getter_name":"reasonsJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL DEFAULT '[]'","default_dart":"const CustomExpression('\\'[]\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarAuthorizationReasonsConverter()","dart_type_name":"List"}},{"name":"errors_json","getter_name":"errorsJson","moor_type":"string","nullable":false,"customConstraints":"NOT NULL DEFAULT '[]'","default_dart":"const CustomExpression('\\'[]\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CedarAuthorizationErrorsConverter()","dart_type_name":"AuthorizationErrors"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":33,"references":[0],"type":"table","data":{"name":"celest_auth_sessions","was_declared_in_moor":true,"columns":[{"name":"rowid","getter_name":"rowid","moor_type":"int","nullable":false,"customConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"session_id","getter_name":"sessionId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL UNIQUE","default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"session_key","getter_name":"sessionKey","moor_type":"blob","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"ip_address","getter_name":"ipAddress","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"session_pb","getter_name":"sessionPb","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"create_time","getter_name":"createTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]},{"name":"update_time","getter_name":"updateTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]},{"name":"expire_time","getter_name":"expireTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"cancel_time","getter_name":"cancelTime","moor_type":"dateTime","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["CONSTRAINT celest_auth_sessions_user_fk FOREIGN KEY(user_id)REFERENCES users(id)ON DELETE CASCADE"]}},{"id":34,"references":[33],"type":"index","data":{"on":33,"name":"celest_auth_sessions_user_idx","sql":"CREATE INDEX IF NOT EXISTS celest_auth_sessions_user_idx ON celest_auth_sessions(user_id);","unique":false,"columns":[]}},{"id":35,"references":[33],"type":"trigger","data":{"on":33,"references_in_body":[33],"name":"auth_sessions_update_time","sql":"CREATE TRIGGER IF NOT EXISTS auth_sessions_update_time\nAFTER UPDATE ON celest_auth_sessions\nBEGIN\n UPDATE celest_auth_sessions\n SET update_time = unixepoch('now', 'subsec')\n WHERE rowid = OLD.rowid;\nEND;"}},{"id":36,"references":[33],"type":"table","data":{"name":"celest_auth_otp_codes","was_declared_in_moor":true,"columns":[{"name":"session_id","getter_name":"sessionId","moor_type":"int","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"resend_attempt","getter_name":"resendAttempt","moor_type":"int","nullable":false,"customConstraints":"NOT NULL DEFAULT 0","default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"verify_attempt","getter_name":"verifyAttempt","moor_type":"int","nullable":false,"customConstraints":"NOT NULL DEFAULT 0","default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":["CONSTRAINT celest_auth_otp_codes_session_fk FOREIGN KEY(session_id)REFERENCES celest_auth_sessions(\"rowid\")ON DELETE CASCADE"]}},{"id":37,"references":[36],"type":"index","data":{"on":36,"name":"celest_auth_otp_codes_session_idx","sql":"CREATE INDEX IF NOT EXISTS celest_auth_otp_codes_session_idx ON celest_auth_otp_codes(session_id);","unique":false,"columns":[]}},{"id":38,"references":[3],"type":"table","data":{"name":"celest_auth_corks","was_declared_in_moor":true,"columns":[{"name":"cork_id","getter_name":"corkId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL PRIMARY KEY","default_dart":null,"default_client_dart":null,"dsl_features":["primary-key"]},{"name":"cork_key","getter_name":"corkKey","moor_type":"blob","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bearer_type","getter_name":"bearerType","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bearer_id","getter_name":"bearerId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"create_time","getter_name":"createTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]},{"name":"expire_time","getter_name":"expireTime","moor_type":"dateTime","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_use_time","getter_name":"lastUseTime","moor_type":"dateTime","nullable":false,"customConstraints":"NOT NULL DEFAULT (unixepoch('now', 'subsec'))","default_dart":"const CustomExpression('unixepoch(\\'now\\', \\'subsec\\')')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":["CONSTRAINT celest_auth_corks_bearer_fk FOREIGN KEY(bearer_type, bearer_id)REFERENCES cedar_entities(entity_type, entity_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED"]}},{"id":39,"references":[38],"type":"index","data":{"on":38,"name":"celest_auth_corks_bearer_idx","sql":"CREATE INDEX IF NOT EXISTS celest_auth_corks_bearer_idx ON celest_auth_corks(bearer_type, bearer_id);","unique":false,"columns":[]}}]} \ No newline at end of file diff --git a/services/celest_cloud_auth/lib/celest_cloud_auth.dart b/services/celest_cloud_auth/lib/celest_cloud_auth.dart new file mode 100644 index 00000000..db14e091 --- /dev/null +++ b/services/celest_cloud_auth/lib/celest_cloud_auth.dart @@ -0,0 +1,162 @@ +import 'package:cedar/cedar.dart'; +import 'package:celest/src/core/context.dart' as celest; +// ignore: invalid_use_of_internal_member +import 'package:celest/src/runtime/http/cloud_middleware.dart'; +import 'package:celest_cloud_auth/src/authentication/authentication_service.dart'; +import 'package:celest_cloud_auth/src/authorization/authorization_middleware.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/authorization/corks_repository.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_model.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_repository.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/email/email_provider.dart'; +import 'package:celest_cloud_auth/src/model/route_map.dart'; +import 'package:celest_cloud_auth/src/otp/otp_repository.dart'; +import 'package:celest_cloud_auth/src/users/users_service.dart'; +import 'package:celest_core/_internal.dart'; +import 'package:meta/meta.dart'; +import 'package:shelf/shelf.dart'; + +export 'package:celest_cloud_auth/src/database/auth_database.dart'; +export 'package:celest_cloud_auth/src/email/email_provider.dart'; +export 'package:celest_cloud_auth/src/otp/otp_provider.dart'; + +/// The Celest authentication and authorization service. +final class CelestCloudAuth { + CelestCloudAuth._({ + required this.db, + required this.cryptoKeys, + }) { + celest.context.put(contextKey, this); + } + + /// Returns the [CelestCloudAuth] instance from the given [context]. + factory CelestCloudAuth.of(celest.Context context) { + return context.expect(contextKey); + } + + static const celest.ContextKey contextKey = + celest.ContextKey('CelestCloudAuth'); + + /// Creates a new [CelestCloudAuth] instance which can be added to a Shelf + /// pipeline using [handler]. + /// + /// Typical usage: + /// + /// ```dart + /// Future main() async { + /// final auth = await CelestCloudAuth.create( + /// database: AuthDatabase.memory(), + /// ); + /// + /// // Adds authentication routes. + /// final router = Router(); + /// router.mount('/v1alpha1/auth/', auth.handler); + /// + /// // Adds authorization middleware. + /// final pipeline = const Pipeline().addMiddleware(auth.middleware); + /// + /// final server = await serve( + /// pipeline.addHandler(router.call), + /// InternetAddress.anyIpV4, + /// 8080, + /// ); + /// print('Serving at http://${server.address.host}:${server.port}'); + /// } + /// ``` + static Future create({ + required AuthDatabase database, + EmailOtpProvider? emailProvider, + }) async { + await database.ping(); + final cryptoKeys = await CryptoKeyRepository.create(db: database); + if (emailProvider != null) { + celest.context.put(contextKeyEmailOtpProvider, emailProvider); + } + return CelestCloudAuth._( + db: database, + cryptoKeys: cryptoKeys, + ); + } + + @visibleForTesting + static Future test({ + AuthDatabase? db, + CryptoKey? rootKey, + }) async { + db ??= AuthDatabase.memory(); + await db.ping(); + final cryptoKeys = await CryptoKeyRepository.create( + db: db, + rootKey: rootKey, + ); + return CelestCloudAuth._( + db: db, + cryptoKeys: cryptoKeys, + ); + } + + /// An authorization middleware which can be added to a Shelf pipeline. + AuthorizationMiddleware get middleware => AuthorizationMiddleware( + routeMap: routeMap, + corks: corks, + db: db, + authorizer: authorizer, + ); + + @visibleForTesting + final AuthDatabase db; + + @visibleForTesting + final CryptoKeyRepository cryptoKeys; + + @visibleForTesting + OtpRepository get otp => OtpRepository(cryptoKeys: cryptoKeys, db: db); + + @visibleForTesting + Authorizer get authorizer => Authorizer(db: db); + + @visibleForTesting + CorksRepository get corks => CorksRepository( + issuer: context.rootEntity, + db: db, + cryptoKeys: cryptoKeys, + ); + + /// A map of routes to their respective [EntityUid]s. + @visibleForTesting + late final RouteMap routeMap = RouteMap.of(celest.context.project); + + @visibleForTesting + late final AuthenticationService authentication = AuthenticationService( + routeMap: routeMap, + otp: otp, + authorizer: authorizer, + corks: corks, + cryptoKeys: cryptoKeys, + db: db, + ); + + @visibleForTesting + late final UsersService users = UsersService( + routeMap: routeMap, + authorizer: authorizer, + corks: corks, + db: db, + ); + + Handler get handler { + final pipeline = + const Pipeline().addMiddleware(const CloudExceptionMiddleware().call); + final cascade = Cascade(statusCodes: [HttpStatus.notFound]) + .add(authentication.handler) + .add(users.handler) + .handler; + return pipeline.addHandler(cascade); + } + + Future close() async { + await db.close(); + } +} diff --git a/services/celest_cloud_auth/lib/src/authentication/authentication_model.dart b/services/celest_cloud_auth/lib/src/authentication/authentication_model.dart new file mode 100644 index 00000000..cf62ac2f --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authentication/authentication_model.dart @@ -0,0 +1,366 @@ +import 'dart:typed_data'; + +import 'package:cedar/cedar.dart'; +import 'package:celest_cloud/src/proto.dart' as pb; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/model/interop.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:protobuf/protobuf.dart'; + +sealed class AuthenticationFactor { + const AuthenticationFactor(); + + factory AuthenticationFactor.fromProto(pb.AuthenticationFactor proto) { + return switch (proto.whichFactor()) { + pb.AuthenticationFactor_Factor.emailOtp => + AuthenticationFactorEmailOtp.fromProto(proto.emailOtp), + pb.AuthenticationFactor_Factor.smsOtp => + AuthenticationFactorSmsOtp.fromProto(proto.smsOtp), + final unknown => throw ArgumentError.value( + unknown, + 'factor', + 'Invalid AuthenticationFactor. Expected one of: emailOtp, smsOtp', + ), + }; + } + + pb.AuthenticationFactor toProto() => switch (this) { + final AuthenticationFactorEmailOtp emailOtp => pb.AuthenticationFactor( + emailOtp: emailOtp.toValueProto(), + ), + final AuthenticationFactorSmsOtp smsOtp => pb.AuthenticationFactor( + smsOtp: smsOtp.toValueProto(), + ), + }; + + GeneratedMessage toValueProto(); + + @override + String toString() => toProto().toString(); +} + +final class AuthenticationFactorEmailOtp extends AuthenticationFactor { + const AuthenticationFactorEmailOtp({ + required this.email, + this.code, + }); + + factory AuthenticationFactorEmailOtp.fromProto( + pb.AuthenticationFactorEmailOtp emailOtp, + ) { + if (!emailOtp.hasEmail()) { + throw const BadRequestException( + 'Missing email in AuthenticationFactorEmailOtp', + ); + } + return AuthenticationFactorEmailOtp( + email: emailOtp.email, + code: emailOtp.hasCode() ? emailOtp.code : null, + ); + } + + final String email; + final String? code; + + @override + pb.AuthenticationFactorEmailOtp toValueProto() => + pb.AuthenticationFactorEmailOtp( + email: email, + code: code, + ); +} + +final class AuthenticationFactorSmsOtp extends AuthenticationFactor { + const AuthenticationFactorSmsOtp({ + required this.phoneNumber, + this.code, + }); + + factory AuthenticationFactorSmsOtp.fromProto( + pb.AuthenticationFactorSmsOtp smsOtp, + ) { + if (!smsOtp.hasPhoneNumber()) { + throw const BadRequestException( + 'Missing phoneNumber in AuthenticationFactorSmsOtp', + ); + } + return AuthenticationFactorSmsOtp( + phoneNumber: smsOtp.phoneNumber, + code: smsOtp.hasCode() ? smsOtp.code : null, + ); + } + + final String phoneNumber; + final String? code; + + @override + pb.AuthenticationFactorSmsOtp toValueProto() => pb.AuthenticationFactorSmsOtp( + phoneNumber: phoneNumber, + code: code, + ); +} + +final class SessionClient { + const SessionClient({ + required this.clientId, + this.clientType, + required this.callbacks, + }); + + factory SessionClient.fromProto(pb.SessionClient client) { + return SessionClient( + clientId: client.clientId, + clientType: client.hasClientType() ? client.clientType : null, + callbacks: SessionCallbacks.fromProto(client.callbacks), + ); + } + + final String clientId; + final pb.ClientType? clientType; + final SessionCallbacks callbacks; + + pb.SessionClient toProto() => pb.SessionClient( + clientId: clientId, + clientType: clientType, + callbacks: callbacks.toProto(), + ); + + @override + String toString() => toProto().toString(); +} + +final class SessionCallbacks { + const SessionCallbacks({ + required this.successUri, + this.errorUri, + }); + + factory SessionCallbacks.fromProto(pb.SessionCallbacks callbacks) { + return SessionCallbacks( + successUri: + callbacks.hasSuccessUri() ? Uri.parse(callbacks.successUri) : null, + errorUri: callbacks.hasErrorUri() ? Uri.parse(callbacks.errorUri) : null, + ); + } + + final Uri? successUri; + final Uri? errorUri; + + pb.SessionCallbacks toProto() => pb.SessionCallbacks( + successUri: successUri.toString(), + errorUri: errorUri?.toString(), + ); + + @override + String toString() => toProto().toString(); +} + +sealed class SessionState { + const SessionState(); + + void apply(pb.Session session); + + GeneratedMessage toProto(); + + @override + String toString() => toProto().toString(); +} + +final class SessionStateSuccess extends SessionState { + const SessionStateSuccess({ + required this.cork, + required this.user, + required this.isNewUser, + }); + + factory SessionStateSuccess.fromProto(pb.AuthenticationSuccess success) { + return SessionStateSuccess( + cork: Cork.parse(success.identityToken), + user: success.user.toModel(), + isNewUser: success.isNewUser, + ); + } + + final Cork cork; + String get identityToken => cork.toString(); + + final User user; + final bool isNewUser; + + @override + void apply(pb.Session session) { + session.success = toProto(); + } + + @override + pb.AuthenticationSuccess toProto() => pb.AuthenticationSuccess( + identityToken: cork.toString(), + user: user.toProto(), + isNewUser: isNewUser, + ); +} + +sealed class SessionStateNextStep extends SessionState { + const SessionStateNextStep(); + + factory SessionStateNextStep.fromProto(pb.AuthenticationStep proto) { + return switch (proto.whichStep()) { + pb.AuthenticationStep_Step.needsProof => + SessionStateNeedsProof.fromProto(proto.needsProof), + pb.AuthenticationStep_Step.pendingConfirmation => + SessionStatePendingConfirmation.fromProto(proto.pendingConfirmation), + final unknown => throw ArgumentError.value( + unknown, + 'step', + 'Invalid AuthenticationStep. Expected one of: needsProof, pendingConfirmation', + ), + }; + } + + @override + pb.AuthenticationStep toProto() => switch (this) { + final SessionStateNeedsProof needsProof => pb.AuthenticationStep( + needsProof: needsProof.toValueProto(), + ), + final SessionStatePendingConfirmation pendingConfirmation => + pb.AuthenticationStep( + pendingConfirmation: pendingConfirmation.toValueProto(), + ), + }; + + @override + void apply(pb.Session session) { + session.nextStep = toProto(); + } +} + +final class SessionStateNeedsProof extends SessionStateNextStep { + const SessionStateNeedsProof({ + required this.factor, + }); + factory SessionStateNeedsProof.fromProto(pb.AuthenticationFactor proto) { + return SessionStateNeedsProof( + factor: AuthenticationFactor.fromProto(proto), + ); + } + + final AuthenticationFactor factor; + + pb.AuthenticationFactor toValueProto() => factor.toProto(); +} + +final class SessionStatePendingConfirmation extends SessionStateNextStep { + const SessionStatePendingConfirmation({ + this.linkExistingUser, + this.registerUser, + }); + + factory SessionStatePendingConfirmation.fromProto( + pb.AuthenticationPendingConfirmation proto, + ) { + return SessionStatePendingConfirmation( + linkExistingUser: + proto.hasLinkExistingUser() ? proto.linkExistingUser.toModel() : null, + registerUser: + proto.hasRegisterUser() ? proto.registerUser.toModel() : null, + ); + } + + final User? linkExistingUser; + final User? registerUser; + + pb.AuthenticationPendingConfirmation toValueProto() => + pb.AuthenticationPendingConfirmation( + linkExistingUser: linkExistingUser?.toProto(), + registerUser: registerUser?.toProto(), + ); +} + +final class Session { + Session({ + EntityUid? parent, + required String sessionId, + required this.cryptoKeyId, + this.userId, + required this.expireTime, + String? sessionToken, + required this.authenticationFactor, + this.state, + required this.clientInfo, + this.ipAddress, + this.externalSessionId, + }) : parent = parent ?? context.rootEntity, + sessionId = TypeId.decode(sessionId), + _sessionToken = sessionToken; + + const Session._({ + required this.parent, + required this.sessionId, + required this.cryptoKeyId, + required this.userId, + required this.expireTime, + required String? sessionToken, + required this.authenticationFactor, + required this.state, + required this.clientInfo, + required this.ipAddress, + required this.externalSessionId, + }) : _sessionToken = sessionToken; + + final EntityUid? parent; + final TypeId sessionId; + final Uint8List cryptoKeyId; + final String? userId; + final DateTime expireTime; + final String? _sessionToken; + String get sessionToken => _sessionToken!; + + final AuthenticationFactor authenticationFactor; + final SessionState? state; + final SessionClient clientInfo; + final String? ipAddress; + final String? externalSessionId; + + pb.Session toProto({ + String? sessionToken, + }) { + final session = pb.Session( + parent: parent?.id, + sessionId: sessionId.encoded, + sessionToken: sessionToken ?? this.sessionToken, + expireTime: expireTime.toProto(), + client: clientInfo.toProto(), + ); + state?.apply(session); + return session; + } + + Session copyWith({ + EntityUid? parent, + Uint8List? cryptoKeyId, + String? userId, + DateTime? expireTime, + String? sessionToken, + AuthenticationFactor? authenticationFactor, + SessionState? state, + SessionClient? clientInfo, + String? ipAddress, + String? externalSessionId, + }) { + return Session._( + parent: parent ?? this.parent, + sessionId: sessionId, + cryptoKeyId: cryptoKeyId ?? this.cryptoKeyId, + userId: userId ?? this.userId, + expireTime: expireTime ?? this.expireTime, + sessionToken: sessionToken ?? _sessionToken, + authenticationFactor: authenticationFactor ?? this.authenticationFactor, + state: state ?? this.state, + clientInfo: clientInfo ?? this.clientInfo, + ipAddress: ipAddress ?? this.ipAddress, + externalSessionId: externalSessionId ?? this.externalSessionId, + ); + } +} diff --git a/services/celest_cloud_auth/lib/src/authentication/authentication_service.dart b/services/celest_cloud_auth/lib/src/authentication/authentication_service.dart new file mode 100644 index 00000000..0531e817 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authentication/authentication_service.dart @@ -0,0 +1,508 @@ +import 'package:cedar/cedar.dart'; +// ignore: invalid_use_of_internal_member +import 'package:celest/src/runtime/http/cloud_middleware.dart'; +import 'package:celest_ast/celest_ast.dart'; +import 'package:celest_cloud/src/proto.dart' as pb; +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_cloud_auth/src/authorization/authorization_middleware.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/authorization/corks_repository.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_repository.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/http/http_helpers.dart'; +import 'package:celest_cloud_auth/src/model/route_map.dart'; +import 'package:celest_cloud_auth/src/otp/otp_repository.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:meta/meta.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +typedef _Deps = ({ + RouteMap routeMap, + AuthDatabase db, + OtpRepository otp, + CryptoKeyRepository cryptoKeys, + Authorizer authorizer, + CorksRepository corks, +}); + +extension type AuthenticationService._(_Deps _deps) implements Object { + AuthenticationService({ + required RouteMap routeMap, + required AuthDatabase db, + required OtpRepository otp, + required CryptoKeyRepository cryptoKeys, + required Authorizer authorizer, + required CorksRepository corks, + }) : this._( + ( + routeMap: routeMap, + db: db, + otp: otp, + cryptoKeys: cryptoKeys, + authorizer: authorizer, + corks: corks, + ), + ); + + AuthDatabase get _db => _deps.db; + OtpRepository get _otp => _deps.otp; + CryptoKeyRepository get _cryptoKeys => _deps.cryptoKeys; + CorksRepository get _corks => _deps.corks; + + static const String apiId = 'celest.cloud.auth.v1alpha1.Authentication'; + static const EntityUid apiUid = EntityUid.of('Celest::Api', apiId); + + static final ResolvedApi api = ResolvedApi( + apiId: apiId, + functions: { + 'GetOpenIdUserInfo': ResolvedCloudFunction( + apiId: apiId, + functionId: 'GetOpenIdUserInfo', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'GET', + path: '/v1alpha1/auth/userinfo', + ), + ), + ), + 'StartSession': ResolvedCloudFunction( + apiId: apiId, + functionId: 'StartSession', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'POST', + path: '/v1alpha1/auth/sessions:startSession', + ), + ), + ), + 'ContinueSession': ResolvedCloudFunction( + apiId: apiId, + functionId: 'ContinueSession', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'POST', + path: '/v1alpha1/auth/sessions:continueSession', + ), + ), + ), + 'EndSession': ResolvedCloudFunction( + apiId: apiId, + functionId: 'EndSession', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'POST', + path: '/v1alpha1/auth/sessions:endSession', + ), + ), + ), + }, + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: apiId, + values: { + SlotId.resource: apiUid, + }, + ), + ], + ), + ); + + Router get _router { + return Router() + ..get('/userinfo', handleGetOpenIdUserInfo) + // Shelf doesn't like `:` in the path, but will route when escaped + ..post('/sessions%3AstartSession', handleStartSession) + ..post('/sessions%3AcontinueSession', handleContinueSession) + ..post('/sessions%3AendSession', handleEndSession); + } + + Handler get handler { + final requestAuthorizer = AuthorizationMiddleware( + routeMap: _deps.routeMap, + corks: _deps.corks, + db: _deps.db, + authorizer: _deps.authorizer, + ); + return const Pipeline() + .addMiddleware(const CloudExceptionMiddleware().call) + .addMiddleware(requestAuthorizer.call) + .addHandler(_router.call); + } + + @visibleForTesting + Future getUserInfo() async { + final user = context.get(ContextKey.principal); + if (user == null) { + throw const UnauthorizedException(null); + } + return pb.OpenIdUserinfo( + sub: user.userId, + email: user.emails.primary?.email, + emailVerified: user.emails.primary?.isVerified, + phoneNumber: user.phoneNumbers.primary?.phoneNumber, + phoneNumberVerified: user.phoneNumbers.primary?.isVerified, + familyName: user.familyName, + givenName: user.givenName, + locale: user.languageCode, + zoneinfo: user.timeZone, + updatedAt: switch (user.updateTime) { + final updatedAt? => updatedAt.millisecondsSinceEpoch ~/ 1000, + _ => null, + }, + ); + } + + Future _createSession({ + required TypeId sessionId, + required String? userId, + required AuthenticationFactor factor, + required SessionClient clientInfo, + DateTime? expireTime, + String? ipAddress, + }) async { + expireTime ??= DateTime.timestamp().add(const Duration(minutes: 15)); + return _db.transaction(() async { + final keyData = await _cryptoKeys.mintHmacKey( + cryptoKeyId: sessionId.uuid.value, + ); + final session = await _db.authDrift.createSession( + sessionId: sessionId.encoded, + cryptoKeyId: keyData.cryptoKeyId, + userId: userId, + expireTime: expireTime!, + authenticationFactor: factor, + clientInfo: clientInfo, + ipAddress: ipAddress, + ); + return session.first; + }); + } + + Future handleGetOpenIdUserInfo(Request request) async { + final userInfo = await getUserInfo(); + return userInfo.jsonResponse(); + } + + @visibleForTesting + Future startSession({ + required AuthenticationFactor factor, + required SessionClient clientInfo, + String? ipAddress, + }) async { + final sessionId = TypeId(); + var user = context.get(ContextKey.principal); + if (user == null) { + switch (factor) { + case AuthenticationFactorEmailOtp(:final email): + user = await _db.findUserByEmail(email: email); + case AuthenticationFactorSmsOtp(:final phoneNumber): + user = await _db.findUserByPhoneNumber(phoneNumber: phoneNumber); + } + } + await _createSession( + sessionId: sessionId, + userId: user?.userId, + factor: factor, + clientInfo: clientInfo, + ipAddress: ipAddress, + ); + + try { + final nextStep = await _sendOtp( + sessionId: sessionId, + factor: factor, + resend: null, + ); + return await _db.transaction(() async { + final session = await _updateSessionState( + sessionId: sessionId, + state: nextStep, + ); + final sessionToken = await _corks.createSessionCork(session: session); + return session.copyWith(sessionToken: sessionToken.toString()); + }); + } on Object { + await _db.authDrift.deleteSession(sessionId: sessionId.encoded); + rethrow; + } + } + + Future handleStartSession(Request request) async { + final jsonRequest = await JsonUtf8.decodeStream(request.read()); + final pbRequest = pb.StartSessionRequest() + ..mergeFromProto3Json(jsonRequest); + final factor = AuthenticationFactor.fromProto( + pb.AuthenticationFactor( + emailOtp: pbRequest.hasEmailOtp() ? pbRequest.emailOtp : null, + smsOtp: pbRequest.hasSmsOtp() ? pbRequest.smsOtp : null, + ), + ); + final clientInfo = SessionClient.fromProto(pbRequest.client); + final session = await startSession( + factor: factor, + clientInfo: clientInfo, + ipAddress: request.clientIp, + ); + return session.toProto().jsonResponse(); + } + + Future _authenticateUser({ + required String userId, + }) async { + final user = await _db.getUser(userId: userId); + if (user == null) { + throw const NotFoundException('User not found'); + } + final cork = await _corks.createUserCork(user: user); + return SessionStateSuccess( + cork: cork, + user: user, + isNewUser: false, + ); + } + + Future _createUser({ + required AuthenticationFactor factor, + }) async { + final (user, cork) = await _db.transaction(() async { + final user = await _db.createUser( + user: User( + userId: typeId(), + emails: [ + if (factor case AuthenticationFactorEmailOtp(:final email)) + Email(email: email, isVerified: true, isPrimary: true), + ], + phoneNumbers: [ + if (factor case AuthenticationFactorSmsOtp(:final phoneNumber)) + PhoneNumber( + phoneNumber: phoneNumber, + isVerified: true, + isPrimary: true, + ), + ], + roles: const [EntityUid.of('Celest::Role', 'authenticated')], + ), + ); + final cork = await _corks.createUserCork(user: user); + return (user, cork); + }); + return SessionStateSuccess( + cork: cork, + user: user, + isNewUser: true, + ); + } + + Future _updateSessionState({ + required TypeId sessionId, + required T state, + }) async { + final updated = await _db.authDrift.updateSession( + sessionId: sessionId.encoded, + state: state, + ); + return updated.first; + } + + Future _verifyOtp({ + required Session session, + required AuthenticationFactor factor, + required AuthenticationFactor proof, + }) async { + switch ((factor, proof)) { + case ( + AuthenticationFactorEmailOtp(), + AuthenticationFactorEmailOtp(:final code) + ): + if (code == null) { + throw const BadRequestException('Code is required'); + } + final (ok, _) = await _otp.verify( + sessionId: session.sessionId, + code: code, + ); + if (!ok) { + throw const BadRequestException('Invalid code'); + } + case ( + AuthenticationFactorSmsOtp(), + AuthenticationFactorSmsOtp(:final code) + ): + if (code == null) { + throw const BadRequestException('Code is required'); + } + final (ok, _) = await _otp.verify( + sessionId: session.sessionId, + code: code, + ); + if (!ok) { + throw const BadRequestException('Invalid code'); + } + default: + throw BadRequestException( + 'Invalid proof. Expected ${factor.runtimeType}', + ); + } + if (session.userId case final userId?) { + return _authenticateUser(userId: userId); + } + return _createUser( + factor: factor, + ); + } + + Future _sendOtp({ + required TypeId sessionId, + required AuthenticationFactor factor, + required AuthenticationFactor? resend, + }) async { + switch ((factor, resend)) { + case ( + final AuthenticationFactorEmailOtp factor, + AuthenticationFactorEmailOtp() || null + ): + final (ok, nextResend) = await _otp.send( + sessionId: sessionId, + to: factor.email, + provider: context.email, + ); + if (!ok) { + if (nextResend == null) { + throw const ResourceExhaustedException( + 'Failed to send OTP. Please restart the authentication flow.', + ); + } + final resendIn = nextResend.difference(DateTime.timestamp()); + throw ResourceExhaustedException( + 'Failed to send OTP. Try again in ${resendIn.inSeconds} seconds', + ); + } + case (AuthenticationFactorSmsOtp(), AuthenticationFactorSmsOtp() || null): + // final (ok, nextResend) = await _otp.send( + // sessionId: sessionId, + // to: factor.phoneNumber, + // provider: _otpProviders.sms, + // ); + // if (!ok) { + // if (nextResend == null) { + // throw const ResourceExhaustedException( + // 'Failed to send OTP. Please restart the authentication flow.', + // ); + // } + // final resendIn = nextResend.difference(DateTime.timestamp()); + // throw ResourceExhaustedException( + // 'Failed to send OTP. Try again in ${resendIn.inSeconds} seconds', + // ); + // } + throw UnimplementedError('SMS OTP is not implemented'); + default: + throw BadRequestException( + 'Invalid resend. Expected ${factor.runtimeType}', + ); + } + return SessionStateNeedsProof( + factor: factor, + ); + } + + @visibleForTesting + Future continueSession({ + required TypeId sessionId, + required String sessionToken, + AuthenticationFactor? proof, + SessionStatePendingConfirmation? confirmation, + AuthenticationFactor? resend, + }) async { + final session = await _db.authDrift + .getSession(sessionId: sessionId.encoded) + .getSingleOrNull(); + if (session == null) { + throw const NotFoundException('Session not found'); + } + final sessionCork = CedarCork.parse(sessionToken); + await _corks.verify(cork: sessionCork); + + final updatedState = await switch (session.state) { + SessionStateNeedsProof(:final factor) => switch ((proof, resend)) { + (final proof?, _) => _verifyOtp( + session: session, + factor: factor, + proof: proof, + ), + (_, final resend?) => _sendOtp( + sessionId: sessionId, + factor: factor, + resend: resend, + ), + _ => throw const BadRequestException('Proof is required'), + }, + SessionStatePendingConfirmation() => throw UnimplementedError(), + SessionStateSuccess() => throw StateError('Session already completed'), + null => throw StateError('Unexpected state'), + }; + + return _updateSessionState( + sessionId: sessionId, + state: updatedState, + ); + } + + Future handleContinueSession(Request request) async { + final jsonRequest = await JsonUtf8.decodeStream(request.read()); + final pbRequest = pb.ContinueSessionRequest() + ..mergeFromProto3Json(jsonRequest); + final session = await continueSession( + sessionId: TypeId.decode(pbRequest.sessionId), + sessionToken: pbRequest.sessionToken, + proof: pbRequest.hasProof() + ? AuthenticationFactor.fromProto(pbRequest.proof) + : null, + confirmation: pbRequest.hasConfirmation() + ? SessionStatePendingConfirmation.fromProto( + pbRequest.confirmation, + ) + : null, + resend: pbRequest.hasResend() + ? AuthenticationFactor.fromProto(pbRequest.resend) + : null, + ); + return session.toProto(sessionToken: pbRequest.sessionToken).jsonResponse(); + } + + @visibleForTesting + Future endSession({ + required TypeId sessionId, + required String sessionToken, + }) async { + final session = await _db.authDrift + .getSession(sessionId: sessionId.encoded) + .getSingleOrNull(); + if (session == null) { + throw const NotFoundException('Session not found'); + } + final sessionCork = CedarCork.parse(sessionToken); + await _corks.verify(cork: sessionCork); + + await _db.authDrift.deleteSession(sessionId: sessionId.encoded); + } + + Future handleEndSession(Request request) async { + final jsonRequest = await JsonUtf8.decodeStream(request.read()); + final pbRequest = pb.EndSessionRequest()..mergeFromProto3Json(jsonRequest); + await endSession( + sessionId: TypeId.decode(pbRequest.sessionId), + sessionToken: pbRequest.sessionToken, + ); + final response = pb.EndSessionResponse( + sessionId: pbRequest.sessionId, + success: pb.Empty(), + ); + return response.jsonResponse(); + } +} diff --git a/services/celest_cloud_auth/lib/src/authorization/authorization_middleware.dart b/services/celest_cloud_auth/lib/src/authorization/authorization_middleware.dart new file mode 100644 index 00000000..760750cd --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/authorization_middleware.dart @@ -0,0 +1,133 @@ +import 'package:cedar/cedar.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/authorization/corks_repository.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/http/http_helpers.dart'; +import 'package:celest_cloud_auth/src/model/route_map.dart'; +import 'package:celest_core/celest_core.dart' as core; +import 'package:celest_core/celest_core.dart'; +import 'package:collection/collection.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:meta/meta.dart'; +import 'package:shelf/shelf.dart' show Handler, Request; + +typedef _Deps = ({ + RouteMap routeMap, + CorksRepository corks, + AuthDatabase db, + Authorizer authorizer, +}); + +/// {@template celest_cloud_auth.request_authorizer} +/// A middleware that authorizes requests based on the current policy set. +/// {@endtemplate} +extension type AuthorizationMiddleware._(_Deps _deps) implements Object { + /// {@macro celest_cloud_auth.request_authorizer} + AuthorizationMiddleware({ + required RouteMap routeMap, + required CorksRepository corks, + required AuthDatabase db, + required Authorizer authorizer, + }) : this._( + ( + routeMap: routeMap, + corks: corks, + db: db, + authorizer: authorizer, + ), + ); + + RouteMap get _routeMap => _deps.routeMap; + CorksRepository get _corks => _deps.corks; + AuthDatabase get _db => _deps.db; + Authorizer get _authorizer => _deps.authorizer; + + Handler call(Handler inner) { + return (request) async { + final user = await authenticate(request); + if (user != null) { + context.put(ContextKey.principal, user); + } + return inner(request); + }; + } + + /// Authenticates the request and returns the user if the request is + /// authorized. + /// + /// Throws an [UnauthorizedException] if the request is not authorized or + /// a [PermissionDeniedException] if the request is not allowed. + @useResult + @visibleForTesting + Future authenticate(Request request) async { + final requestPath = request.requestedUri.path; + EntityUid? function; + for (final MapEntry(key: uid, value: route) in _routeMap.entries) { + if (route.matches(requestPath)) { + function = uid; + break; + } + } + if (function == null) { + throw core.InternalServerError('Route not found: $requestPath'); + } + + final (user, principal) = await extractPrincipal(request); + + // Authorizes the request using the current policy set and the invoking + // `principal`. + await _authorizer.expectAuthorized( + principal: principal, + action: const EntityUid.of('Celest::Action', 'invoke'), + resource: function.uid, + context: { + 'ip': Value.string(request.clientIp), + }, + debug: true, + ); + context.logger.finest('Authorized request'); + + return user; + } + + /// Extracts the user and the Cedar principal from the request. + /// + /// Celest corks can be passed either through a cookie or the Authorization + /// header. By default, Celest will use cookies when the client is a browser + /// and the Authorization header when the client is a non-browser client. + @useResult + @visibleForTesting + Future<(User?, Entity?)> extractPrincipal(Request request) async { + final cookies = request.cookies; + CedarCork? cork; + if (cookies['cork'] case final token?) { + context.logger.finest('Found cork in cookies'); + cork = CedarCork.parse(token); + } else if (request.headers['authorization']?.split(' ') + case [final type, final token] + when equalsIgnoreAsciiCase(type, 'bearer')) { + context.logger.finest('Found cork in headers'); + cork = CedarCork.parse(token); + } + if (cork == null) { + context.logger.finest('No cork found in cookies or headers'); + return const (null, null); + } + try { + await _corks.verify(cork: cork); + } on InvalidSignatureException catch (e) { + context.logger.severe('Invalid cork signature: $cork', e); + throw const UnauthorizedException('Invalid cork signature'); + } + switch (cork.bearer) { + case EntityUid(type: 'Celest::User', id: final userId): + final user = await _db.getUser(userId: userId); + context.logger.finest('Found user for cork: $user'); + return (user, cork.claims); + default: + context.logger.severe('Valid cork but no bearer: $cork'); + return const (null, null); + } + } +} diff --git a/services/celest_cloud_auth/lib/src/authorization/authorizer.dart b/services/celest_cloud_auth/lib/src/authorization/authorizer.dart new file mode 100644 index 00000000..e3e724de --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/authorizer.dart @@ -0,0 +1,118 @@ +import 'dart:async'; + +import 'package:cedar/cedar.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; + +extension type Authorizer._(AuthDatabase _db) implements Object { + Authorizer({required AuthDatabase db}) : this._(db); + + static final Logger _logger = Logger('Celest.Authorizer'); + + Future expectAuthorized({ + Component? principal, + Component? resource, + EntityUid? action, + Map? context, + bool debug = false, + }) async { + final response = await authorize( + principal: principal, + resource: resource, + action: action, + context: context, + debug: debug, + ); + response.expectAuthorized( + request: AuthorizationRequest( + principal: principal?.uid, + resource: resource?.uid, + action: action, + context: context, + ), + ); + } + + @useResult + Future authorize({ + Component? principal, + Component? resource, + EntityUid? action, + Map? context, + bool debug = false, + }) async { + final (policySet, closure) = await ( + _db.effectivePolicySet, + _db.computeRequestClosure( + principal: principal, + resource: resource, + ), + ).wait; + final request = AuthorizationRequest( + principal: principal?.uid, + resource: resource?.uid, + action: action, + context: context, + entities: { + // The principal and resource may represent yet-tobe-created entities. + // Thus, they will not be present in the closure which is pulled from + // the database. We add them here to ensure they are included in the + // authorization decision. + if (principal case final Entity principal) principal.uid: principal, + if (resource case final Entity resource) resource.uid: resource, + ...closure, + }, + ); + final response = policySet.isAuthorized(request); + unawaited(_recordAuthorization(request, response)); + return response; + } + + Future _recordAuthorization( + AuthorizationRequest request, + AuthorizationResponse response, + ) async { + try { + await _db.cedarDrift.recordAuthorization( + principalType: request.principal?.type, + principalId: request.principal?.id, + actionType: request.action?.type, + actionId: request.action?.id, + resourceType: request.resource?.type, + resourceId: request.resource?.id, + contextJson: request.context ?? const {}, + decision: response.decision == Decision.allow, + reasonsJson: response.reasons, + errorsJson: response.errors, + ); + } on Object catch (e, st) { + _logger.severe('Failed to record authorization', e, st); + } + } +} + +extension on AuthorizationResponse { + void expectAuthorized({AuthorizationRequest? request}) { + if (decision != Decision.allow) { + if (request == null) { + throw PermissionDeniedException( + 'Authorization denied', + JsonList(reasons), + ); + } + const unknown = EntityUid.of('', ''); + final EntityUid(type: principalType, id: principalId) = + (request.principal ?? unknown).uid; + final EntityUid(type: resourceType, id: resourceId) = + (request.resource ?? unknown).uid; + final actionId = request.action?.id; + throw PermissionDeniedException( + '${principalType.split('::').last}::"$principalId" ' + 'is not authorized to perform Action::"$actionId" on ' + '${resourceType.split('::').last}::"$resourceId"', + ); + } + } +} diff --git a/services/celest_cloud_auth/lib/src/authorization/cedar/policies.cedar b/services/celest_cloud_auth/lib/src/authorization/cedar/policies.cedar new file mode 100644 index 00000000..3b7dedd9 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/cedar/policies.cedar @@ -0,0 +1,75 @@ +// Admins can perform all admin actions. +@id("cloud.admin") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"create", + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"update", + Celest::Action::"delete", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"admin" }; + +// Editors can perform all edit actions. +@id("cloud.editor") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"update", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"editor" }; + +// Viewers can perform all view actions. +@id("cloud.viewer") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"viewer" }; + +// Users can do anything to their own profiles. +@id("cloud.users.self") +permit ( + principal is Celest::User, + action, + resource is Celest::User +) +when { principal == resource }; + +// Admin functions and APIs. +@id("cloud.functions.admin") +permit ( + principal in Celest::Role::"admin", + action == Celest::Action::"invoke", + resource in ?resource +); + +// Authenticated functions and APIs. +@id("cloud.functions.authenticated") +permit ( + principal in Celest::Role::"authenticated", + action == Celest::Action::"invoke", + resource in ?resource +); + +// Public functions and APIs. +@id("cloud.functions.public") +permit ( + principal, + action == Celest::Action::"invoke", + resource in ?resource +); diff --git a/services/celest_cloud_auth/lib/src/authorization/cedar/schema.cedarschema b/services/celest_cloud_auth/lib/src/authorization/cedar/schema.cedarschema new file mode 100644 index 00000000..f02d6828 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/cedar/schema.cedarschema @@ -0,0 +1,101 @@ +// The namespace for all core types and entities. +// +// Entities in this namespace are intended to be managed exclusively by Celest. +// Centralizing the lifecycle of these entities is the only means of ensuring +// data consistency and integrity, which is crucial for authorization. +// +// Further namespaces can be defined for application components and these may +// reference or link to entities in this namespace. +namespace Celest { + // The service in which all entities are defined. + // + // A service defines the root of the entity hierarchy. Entities can be + // traced back to the service via the parent chain. + entity Service; + + // A role which can be assigned to a user. + // + // Roles provide a means of grouping disparate permissions and principals + // in a loosely-coupled manner, giving flexibilty to how the remaining + // entities are defined and limiting the amount of data needed to perform + // an authorization request. + entity Role in [Service, Role]; // A role can be a parent of another role. + + // A user of the system. + // + // Users can belong to roles and have permissions granted to them via those + // roles. + entity User in [Service, Role]; + + // A session in which a user is authenticated. + // + // Sessions are used to track the state of a user's authentication and + // authorization. They can be used to track the user's activity and + // provide a means of revoking access. + entity Session in [User]; + + // APIs in Celest are a collection of functions which participate in a + // parent-child relationship. The API is the parent and the functions are + // the children. + entity Api in [Service]; + + // Functions in Celest map directly to individual Dart functions defined + // in your project. + entity Function in [Api]; + + // Actions + // + // Actions are the operations that can be performed on resources. By having + // a small, discrete set of which actions can be performed, we can ensure that + // authorization is consistent and predictable. + // + // We can build more complex permissions by grouping actions together into roles + // or one-off policies. + + // The context in which an authorization request is made. + type AuthorizationContext = { + ip?: ipaddr, + }; + + // The generic act of creating a resource. + action create appliesTo { + principal: [Role, User], + resource: [Role, User], + context: AuthorizationContext, + }; + + // The generic act of reading a resource. + action get appliesTo { + principal: [Role, User], + resource: [Role, User], + context: AuthorizationContext, + }; + + // The generic act of listing resources within a collection. + action list appliesTo { + principal: [Role, User], + resource: [Role, User], + context: AuthorizationContext, + }; + + // The generic act of updating a resource. + action update appliesTo { + principal: [Role, User], + resource: [Role, User], + context: AuthorizationContext, + }; + + // The generic act of deleting a resource. + action delete appliesTo { + principal: [Role, User], + resource: [Role, User], + context: AuthorizationContext, + }; + + // The act taken when an entity invokes a cloud function. + action invoke appliesTo { + principal: [Role, User], + resource: [Function], + context: AuthorizationContext, + }; +} diff --git a/services/celest_cloud_auth/lib/src/authorization/cedar_interop.dart b/services/celest_cloud_auth/lib/src/authorization/cedar_interop.dart new file mode 100644 index 00000000..5a4045d0 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/cedar_interop.dart @@ -0,0 +1,14 @@ +import 'package:cedar/cedar.dart'; +import 'package:celest_ast/celest_ast.dart'; + +extension ResolvedProjectUid on ResolvedProject { + EntityUid get uid => EntityUid.of('Celest::Service', projectId); +} + +extension ResolvedApiUid on ResolvedApi { + EntityUid get uid => EntityUid.of('Celest::Api', apiId); +} + +extension ResolvedCloudFunctionUid on ResolvedCloudFunction { + EntityUid get uid => EntityUid.of('Celest::Function', '$apiId/$functionId'); +} diff --git a/services/celest_cloud_auth/lib/src/authorization/celest_action.dart b/services/celest_cloud_auth/lib/src/authorization/celest_action.dart new file mode 100644 index 00000000..1d9e0051 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/celest_action.dart @@ -0,0 +1,21 @@ +import 'package:cedar/cedar.dart'; + +extension type const CelestAction._(EntityUid _) implements EntityUid { + static const CelestAction create = + CelestAction._(EntityUid.of('Celest::Action', 'create')); + + static const CelestAction get = + CelestAction._(EntityUid.of('Celest::Action', 'get')); + + static const CelestAction update = + CelestAction._(EntityUid.of('Celest::Action', 'update')); + + static const CelestAction delete = + CelestAction._(EntityUid.of('Celest::Action', 'delete')); + + static const CelestAction list = + CelestAction._(EntityUid.of('Celest::Action', 'list')); + + static const CelestAction invoke = + CelestAction._(EntityUid.of('Celest::Action', 'invoke')); +} diff --git a/services/celest_cloud_auth/lib/src/authorization/corks_repository.dart b/services/celest_cloud_auth/lib/src/authorization/corks_repository.dart new file mode 100644 index 00000000..25201272 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/corks_repository.dart @@ -0,0 +1,150 @@ +import 'dart:typed_data'; + +import 'package:cedar/cedar.dart'; +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_repository.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/database/schema/auth.drift.dart' as drift; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:drift/drift.dart' as drift; + +typedef _Dependencies = ({ + EntityUid issuer, + AuthDatabase db, + CryptoKeyRepository cryptoKeys, +}); + +extension type CorksRepository._(_Dependencies _deps) implements Object { + CorksRepository({ + required EntityUid issuer, + required AuthDatabase db, + required CryptoKeyRepository cryptoKeys, + }) : this._( + ( + issuer: issuer, + db: db, + cryptoKeys: cryptoKeys, + ), + ); + + EntityUid get issuer => _deps.issuer; + AuthDatabase get _db => _deps.db; + CryptoKeyRepository get _cryptoKeys => _deps.cryptoKeys; + + Future getCork({ + required Uint8List corkId, + }) async { + return _db.authDrift.getCork(corkId: corkId).getSingleOrNull(); + } + + /// Creates a new cork for the given [user]. + Future createUserCork({ + required User user, + EntityUid? audience, + }) async { + final corkId = TypeId().uuid.value; + final userEntity = Entity( + uid: EntityUid.of('Celest::User', user.userId), + parents: [ + ...user.roles, + issuer, + ], + attributes: RecordValue.fromJson(user.toJson()).attributes, + ); + final corkBuilder = CedarCork.builder(corkId) + ..bearer = EntityUid.of('Celest::User', user.userId) + ..issuer = issuer + ..audience = audience ?? issuer + ..claims = userEntity; + final hmacKey = _cryptoKeys.rootKey; + final cork = await corkBuilder.build().sign(hmacKey.signer); + await _db.transaction(() async { + await _db.createEntity(userEntity); + await _db.authDrift.createCork( + corkId: corkId, + cryptoKeyId: hmacKey.cryptoKeyId, + bearerType: 'Celest::User', + bearerId: user.userId, + expireTime: DateTime.timestamp().add(const Duration(days: 30)), + ); + }); + return cork; + } + + /// Creates a new cork for the given [session]. + Future createSessionCork({ + required Session session, + }) async { + final cryptoKey = await _cryptoKeys.getOrMintHmacKey( + cryptoKeyId: session.cryptoKeyId, + ); + final sessionUid = EntityUid.of( + 'Celest::Session', + session.sessionId.encoded, + ); + final sessionEntity = Entity( + uid: sessionUid, + parents: [ + if (session.userId case final userId?) + EntityUid.of('Celest::User', userId), + ], + attributes: { + 'expireTime': Value.integer( + session.expireTime.millisecondsSinceEpoch ~/ 1000, + ), + }, + ); + final corkBuilder = CedarCork.builder(cryptoKey.cryptoKeyId) + ..bearer = sessionUid + ..issuer = issuer + ..audience = issuer + ..claims = sessionEntity; + final cork = await corkBuilder.build().sign(cryptoKey.signer); + await _db.transaction(() async { + await _db.createEntity(sessionEntity); + await _db.authDrift.createCork( + corkId: cork.id, + cryptoKeyId: session.cryptoKeyId, + bearerType: sessionUid.type, + bearerId: sessionUid.id, + expireTime: session.expireTime, + ); + }); + return cork; + } + + /// Verifies the given [cork]. + Future verify({ + required Cork cork, + }) async { + final corkData = await getCork(corkId: cork.id); + if (corkData == null) { + throw const UnauthorizedException('Invalid cork'); + } + recordUse(cork: cork); + final corkKey = await _cryptoKeys.getKey( + cryptoKeyId: corkData.cryptoKeyId, + ); + await cork.verify(corkKey.signer); + } + + /// Records usage of the given [cork] in the database. + void recordUse({ + required Cork cork, + }) async { + try { + final query = _db.update(_db.authDrift.corks) + ..where((tbl) => tbl.corkId.equals(cork.id)); + await query.write( + drift.CorksCompanion( + lastUseTime: drift.Value(DateTime.timestamp()), + ), + ); + } on Object catch (e, st) { + context.logger.severe('Failed to update cork', e, st); + } + } +} diff --git a/services/celest_cloud_auth/lib/src/authorization/policy_set.g.dart b/services/celest_cloud_auth/lib/src/authorization/policy_set.g.dart new file mode 100644 index 00000000..25be3684 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/authorization/policy_set.g.dart @@ -0,0 +1,93 @@ +// This file is generated. To update, run `dart tool/generate_policy_set.dart`. +library; + +import 'package:cedar/cedar.dart'; + +/// The core policy set. +/// +/// Included policies: +/// - policies.cedar +final corePolicySet = PolicySet.parse(corePolicySetCedar); + +/// The core policy set, in Cedar IDL. +const corePolicySetCedar = ''' +// -------------------- // +// -- policies.cedar -- // +// -------------------- // + +// Admins can perform all admin actions. +@id("cloud.admin") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"create", + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"update", + Celest::Action::"delete", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"admin" }; + +// Editors can perform all edit actions. +@id("cloud.editor") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"update", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"editor" }; + +// Viewers can perform all view actions. +@id("cloud.viewer") +permit ( + principal is Celest::User, + action in [ + Celest::Action::"get", + Celest::Action::"list", + Celest::Action::"invoke" + ], + resource +) +when { principal in Celest::Role::"viewer" }; + +// Users can do anything to their own profiles. +@id("cloud.users.self") +permit ( + principal is Celest::User, + action, + resource is Celest::User +) +when { principal == resource }; + +// Admin functions and APIs. +@id("cloud.functions.admin") +permit ( + principal in Celest::Role::"admin", + action == Celest::Action::"invoke", + resource in ?resource +); + +// Authenticated functions and APIs. +@id("cloud.functions.authenticated") +permit ( + principal in Celest::Role::"authenticated", + action == Celest::Action::"invoke", + resource in ?resource +); + +// Public functions and APIs. +@id("cloud.functions.public") +permit ( + principal, + action == Celest::Action::"invoke", + resource in ?resource +); +'''; diff --git a/services/celest_cloud_auth/lib/src/celest/authorization_descriptor.dart b/services/celest_cloud_auth/lib/src/celest/authorization_descriptor.dart new file mode 100644 index 00000000..b4e42bd1 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/celest/authorization_descriptor.dart @@ -0,0 +1,6 @@ +import 'package:cedar/cedar.dart'; + +abstract interface class AuthorizationDescriptor { + /// The Cedar representation of the entity. + Entity get entity; +} diff --git a/services/celest_cloud_auth/lib/src/context.dart b/services/celest_cloud_auth/lib/src/context.dart new file mode 100644 index 00000000..f4e388e1 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/context.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:cedar/cedar.dart'; +import 'package:celest/src/core/context.dart' as celest; +import 'package:celest_cloud_auth/src/authorization/cedar_interop.dart'; +import 'package:celest_cloud_auth/src/email/email_provider.dart'; +import 'package:celest_cloud_auth/src/email/resend_email_provider.dart'; +import 'package:corks_cedar/corks_cedar.dart'; + +export 'package:celest/src/core/context.dart' show ContextKey; + +Context get context => Context._(celest.context); + +extension type Context._(celest.Context _context) implements celest.Context { + Context.of(Zone zone) : this._(celest.Context.of(zone)); + + static Context get root => Context._(celest.Context.root); + + String get hostname => ''; + + EntityUid get rootEntity => project.uid; + + CedarCork? get cork => _context.get(contextKeyCork); + + EmailOtpProvider get email { + if (get(contextKeyEmailOtpProvider) case final provider?) { + return provider; + } + final apiKey = platform.environment['RESEND_API_KEY']; + if (apiKey != null) { + return ResendEmailProvider(apiKey: apiKey, client: httpClient); + } + return const EmailOtpProvider(); + } +} + +const celest.ContextKey contextKeyCork = celest.ContextKey('cork'); +const celest.ContextKey contextKeyEmailOtpProvider = + celest.ContextKey('EmailOtpProvider'); diff --git a/services/celest_cloud_auth/lib/src/crypto/crypto_key_model.dart b/services/celest_cloud_auth/lib/src/crypto/crypto_key_model.dart new file mode 100644 index 00000000..086009cf --- /dev/null +++ b/services/celest_cloud_auth/lib/src/crypto/crypto_key_model.dart @@ -0,0 +1,110 @@ +@internal +library; + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:meta/meta.dart'; + +enum KeyPurpose { + encryption('ENCRYPT_DECRYPT'), + signing('ASYMMETRIC_SIGN'), + mac('MAC'); + + const KeyPurpose(this.name); + + final String name; +} + +enum KeyAlgorithm { + hmacSha256('HMAC_SHA256'), + aesGcm('AES_256_GCM'), + ed25519('EC_SIGN_ED25519'); + + const KeyAlgorithm(this.name); + + final String name; +} + +sealed class CryptoKey { + factory CryptoKey({ + required Uint8List cryptoKeyId, + required String keyPurpose, + required String keyAlgorithm, + Uint8List? keyMaterial, + String? externalCryptoKeyId, + }) { + final purpose = KeyPurpose.values.firstWhere( + (e) => e.name == keyPurpose, + ); + final algorithm = KeyAlgorithm.values.firstWhere( + (e) => e.name == keyAlgorithm, + ); + if (keyMaterial != null) { + return LocalCryptoKey( + cryptoKeyId: cryptoKeyId, + keyPurpose: purpose, + keyAlgorithm: algorithm, + keyMaterial: keyMaterial, + ); + } + return KmsCryptoKey( + cryptoKeyId: cryptoKeyId, + keyPurpose: purpose, + keyAlgorithm: algorithm, + externalCryptoKeyId: externalCryptoKeyId!, + ); + } + + const CryptoKey._({ + required this.cryptoKeyId, + required this.keyPurpose, + required this.keyAlgorithm, + }); + + final Uint8List cryptoKeyId; + final KeyPurpose keyPurpose; + final KeyAlgorithm keyAlgorithm; + Uint8List? get keyMaterial => null; + String? get externalCryptoKeyId => null; + + Signer get signer; + + @override + String toString() => + 'CryptoKey(${keyPurpose.name}): keyId=${base64Encode(cryptoKeyId)}'; +} + +final class LocalCryptoKey extends CryptoKey { + const LocalCryptoKey({ + required super.cryptoKeyId, + required super.keyPurpose, + required super.keyAlgorithm, + required this.keyMaterial, + }) : super._(); + + @override + final Uint8List keyMaterial; + + @override + Signer get signer => Signer(cryptoKeyId, keyMaterial); +} + +final class KmsCryptoKey extends CryptoKey { + const KmsCryptoKey({ + required super.cryptoKeyId, + required super.keyPurpose, + required super.keyAlgorithm, + required this.externalCryptoKeyId, + }) : super._(); + + @override + final String externalCryptoKeyId; + + @override + Signer get signer => throw UnimplementedError(); + + @override + String toString() => '${super.toString()}, externalId=$externalCryptoKeyId'; +} diff --git a/services/celest_cloud_auth/lib/src/crypto/crypto_key_repository.dart b/services/celest_cloud_auth/lib/src/crypto/crypto_key_repository.dart new file mode 100644 index 00000000..0ea3fa49 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/crypto/crypto_key_repository.dart @@ -0,0 +1,79 @@ +import 'package:celest_cloud_auth/src/crypto/crypto_key_model.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/util/random_bytes.dart'; +import 'package:drift/drift.dart'; + +typedef _Deps = ({ + AuthDatabase db, + CryptoKey rootKey, +}); + +extension type CryptoKeyRepository._(_Deps _deps) implements Object { + static Future create({ + required AuthDatabase db, + CryptoKey? rootKey, + }) async { + rootKey ??= LocalCryptoKey( + cryptoKeyId: secureRandomBytes(16), + keyPurpose: KeyPurpose.signing, + keyAlgorithm: KeyAlgorithm.hmacSha256, + keyMaterial: secureRandomBytes(32), + ); + rootKey = (await db.authDrift.createCryptoKey( + cryptoKeyId: rootKey.cryptoKeyId, + keyPurpose: rootKey.keyPurpose.name, + keyAlgorithm: rootKey.keyAlgorithm.name, + keyMaterial: rootKey.keyMaterial, + )) + .first; + return CryptoKeyRepository._( + ( + db: db, + rootKey: rootKey, + ), + ); + } + + AuthDatabase get _db => _deps.db; + CryptoKey get rootKey => _deps.rootKey; + + Future getKey({ + required Uint8List cryptoKeyId, + }) async { + return _db.authDrift.getCryptoKey(cryptoKeyId: cryptoKeyId).getSingle(); + } + + Future insertKey({ + required CryptoKey cryptoKey, + }) async { + final result = await _db.authDrift.createCryptoKey( + cryptoKeyId: cryptoKey.cryptoKeyId, + keyPurpose: cryptoKey.keyPurpose.name, + keyAlgorithm: cryptoKey.keyAlgorithm.name, + keyMaterial: cryptoKey.keyMaterial, + externalCryptoKeyId: cryptoKey.externalCryptoKeyId, + ); + return result.first; + } + + Future getOrMintHmacKey({ + required Uint8List cryptoKeyId, + }) async { + final key = await _db.authDrift + .getCryptoKey(cryptoKeyId: cryptoKeyId) + .getSingleOrNull(); + return key ?? mintHmacKey(cryptoKeyId: cryptoKeyId); + } + + Future mintHmacKey({ + required Uint8List cryptoKeyId, + }) async { + final key = LocalCryptoKey( + cryptoKeyId: cryptoKeyId, + keyPurpose: KeyPurpose.signing, + keyAlgorithm: KeyAlgorithm.hmacSha256, + keyMaterial: secureRandomBytes(32), + ); + return insertKey(cryptoKey: key); + } +} diff --git a/services/celest_cloud_auth/lib/src/crypto/crypto_key_store.dart b/services/celest_cloud_auth/lib/src/crypto/crypto_key_store.dart new file mode 100644 index 00000000..206075ea --- /dev/null +++ b/services/celest_cloud_auth/lib/src/crypto/crypto_key_store.dart @@ -0,0 +1 @@ +abstract interface class CryptoKeyStore {} diff --git a/services/celest_cloud_auth/lib/src/database/auth_database.dart b/services/celest_cloud_auth/lib/src/database/auth_database.dart new file mode 100644 index 00000000..6a75459f --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/auth_database.dart @@ -0,0 +1,659 @@ +import 'package:async/async.dart'; +import 'package:cedar/ast.dart'; +import 'package:cedar/cedar.dart'; +import 'package:celest_ast/celest_ast.dart'; +import 'package:celest_cloud_auth/src/authorization/cedar_interop.dart'; +import 'package:celest_cloud_auth/src/authorization/policy_set.g.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/database/schema/users.drift.dart'; +import 'package:celest_cloud_auth/src/database/schema_versions.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:collection/collection.dart'; +import 'package:crypto/crypto.dart'; +import 'package:drift/drift.dart' hide Component; +import 'package:drift/native.dart'; +import 'package:file/file.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:protobuf/protobuf.dart'; + +import 'auth_database.drift.dart'; + +@DriftDatabase( + include: { + 'schema/auth.drift', + 'schema/cedar.drift', + 'schema/projects.drift', + 'schema/users.drift', + }, +) +class AuthDatabase extends $AuthDatabase { + AuthDatabase(super.e) : _project = null; + + AuthDatabase._(super.e, this._project); + + factory AuthDatabase.localDir( + Directory dir, { + ResolvedProject? project, + bool verbose = false, + }) { + return AuthDatabase._( + _openDirConnection(dir, verbose: verbose), + project, + ); + } + + factory AuthDatabase.memory({ + bool verbose = false, + ResolvedProject? project, + }) { + final nativeDb = NativeDatabase.memory( + logStatements: verbose, + cachePreparedStatements: true, + ); + return AuthDatabase._(nativeDb, project); + } + + static final Logger _logger = Logger('Celest.AuthDatabase'); + + static QueryExecutor _openDirConnection( + Directory dir, { + required bool verbose, + }) { + return LazyDatabase(() async { + final file = dir.childFile('auth.celest.db'); + if (!file.existsSync()) { + await file.create(recursive: true); + } + return NativeDatabase( + file, + logStatements: verbose, + cachePreparedStatements: true, + enableMigrations: true, + ); + }); + } + + @visibleForTesting + static final Map coreEntities = { + const EntityUid.of('Celest::Action', 'create'): const Entity( + uid: EntityUid.of('Celest::Action', 'create'), + ), + const EntityUid.of('Celest::Action', 'get'): const Entity( + uid: EntityUid.of('Celest::Action', 'get'), + ), + const EntityUid.of('Celest::Action', 'list'): const Entity( + uid: EntityUid.of('Celest::Action', 'list'), + ), + const EntityUid.of('Celest::Action', 'update'): const Entity( + uid: EntityUid.of('Celest::Action', 'update'), + ), + const EntityUid.of('Celest::Action', 'delete'): const Entity( + uid: EntityUid.of('Celest::Action', 'delete'), + ), + const EntityUid.of('Celest::Action', 'invoke'): const Entity( + uid: EntityUid.of('Celest::Action', 'invoke'), + ), + const EntityUid.of('Celest::Role', 'anonymous'): const Entity( + uid: EntityUid.of('Celest::Role', 'anonymous'), + ), + const EntityUid.of('Celest::Role', 'authenticated'): const Entity( + uid: EntityUid.of('Celest::Role', 'authenticated'), + parents: [ + EntityUid.of('Celest::Role', 'anonymous'), + ], + ), + const EntityUid.of('Celest::Role', 'admin'): const Entity( + uid: EntityUid.of('Celest::Role', 'admin'), + parents: [ + EntityUid.of('Celest::Role', 'authenticated'), + ], + ), + }; + + final ResolvedProject? _project; + ResolvedProject get project => _project ?? context.project; + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (m) async { + await m.createAll(); + await seed(); + }, + onUpgrade: stepByStep(), + beforeOpen: (details) { + return _withoutForeignKeys(() async { + await upsertProject(); + }); + }, + ); + } + + /// Runs [action] in a context without foreign keys enabled. + Future _withoutForeignKeys(Future Function() action) async { + await customStatement('pragma foreign_keys = OFF'); + R result; + try { + result = await action(); + } finally { + await customStatement('pragma foreign_keys = ON'); + } + return result; + } + + final _effectivePolicySetCache = + AsyncCache(const Duration(minutes: 5)); + + /// The effective [PolicySet] for the project. + Future get effectivePolicySet { + return _effectivePolicySetCache.fetch(loadEffectivePolicySet); + } + + /// Pings the database to ensure a valid connection. + Future ping() async { + await customSelect('SELECT 1').get(); + } + + /// Seeds the database with core types, entities, and relationships. + Future seed() async { + _logger.finer('Seeding database'); + await transaction(() async { + await Future.wait( + coreEntities.values.map(createEntity), + eagerError: true, + ); + await upsertPolicySet(corePolicySet); + }); + } + + /// Loads the effective policy set from the database. + @visibleForTesting + Future loadEffectivePolicySet() async { + _logger.finest('Fetching effective policy set'); + final (policies, templates, templateLinks) = await ( + select(cedarDrift.cedarPolicies).get(), + select(cedarDrift.cedarPolicyTemplates).get(), + select(cedarDrift.cedarPolicyTemplateLinks).get(), + ).wait; + final policySet = PolicySet.build((b) { + for (final policy in policies) { + b.policies[policy.id] = policy.policy; + } + + final templatesById = { + for (final template in templates) + template.templateId: template.template, + }; + b.templates.addAll(templatesById); + for (final link in templateLinks) { + final template = templatesById[link.templateId]; + if (template == null) { + _logger.warning('Template not found for link: $link'); + continue; + } + // TODO(dnys1): Move this logic to `package:cedar`. + b.policies[link.id] = template.rebuild((b) { + if ((link.principalType, link.principalId) + case (final type?, final id?)) { + final principal = EntityUid.of(type, id); + b.principal = switch (template.principal) { + PrincipalAll() => const PrincipalAll(), + PrincipalIn() => PrincipalIn(principal), + PrincipalEquals() => PrincipalEquals(principal), + final PrincipalIs principalIs => principalIs, + PrincipalIsIn(:final entityType) => + PrincipalIsIn(entityType, principal), + }; + } + if ((link.resourceType, link.resourceId) + case (final type?, final id?)) { + final resource = EntityUid.of(type, id); + b.resource = switch (template.resource) { + ResourceAll() => const ResourceAll(), + ResourceIn() => ResourceIn(resource), + ResourceEquals() => ResourceEquals(resource), + final ResourceIs resourceIs => resourceIs, + ResourceIsIn(:final entityType) => + ResourceIsIn(entityType, resource), + }; + } + b.annotations = Annotations({'id': link.policyId}); + }); + } + }); + // _logger.finest(() { + // }); + final policyIds = policySet.policies.entries + .map((pol) => pol.value.id ?? pol.key) + .sorted(); + print('Effective policies: $policyIds'); + return policySet; + } + + static String _etag(GeneratedMessage input) { + final buffer = input.writeToBuffer(); + return md5.convert(buffer).toString(); + } + + /// Records the project's AST in the database. + /// + /// Returns the new, effective policy set for the project. + Future upsertProject() { + _logger.finer('Upserting project: ${project.projectId}'); + + return transaction(() async { + final oldProject = await projectsDrift + .getProject(projectId: project.projectId) + .getSingleOrNull(); + final newProject = (await projectsDrift.upsertProject( + projectId: project.projectId, + version: 'v1', + resolvedAst: project, + etag: _etag(project.toProto()), + )) + .single; + if (oldProject?.etag == newProject.etag) { + _logger.finer('Project AST is up-to-date. Skipping AST upsert.'); + return; + } + await createEntity(Entity(uid: project.uid)); + for (final api in project.apis.values) { + _logger.finer('Upserting API: ${api.apiId}'); + await projectsDrift.upsertApi( + projectId: project.projectId, + apiId: api.apiId, + resolvedAst: api, + etag: _etag(api.toProto()), + ); + await createEntity(Entity(uid: api.uid, parents: [project.uid])); + for (final function in api.functions.values) { + _logger.finer('Upserting function: ${function.functionId}'); + await projectsDrift.upsertFunction( + apiId: api.apiId, + functionId: function.functionId, + resolvedAst: function, + etag: _etag(function.toProto()), + ); + await createEntity(Entity(uid: function.uid, parents: [api.uid])); + } + } + + final differ = _ProjectAuthDiff(); + project.acceptWithArg(differ, oldProject?.resolvedAst); + for (final linkId in differ.removedTemplateLinks) { + _logger.finer('Deleting policy template link: $linkId'); + await cedarDrift.deletePolicyTemplateLink(policyId: linkId); + } + for (final templateId in differ.removedTemplateIds) { + _logger.finer('Deleting policy template: $templateId'); + await cedarDrift.deletePolicyTemplate(templateId: templateId); + } + for (final policyId in differ.removedPolicyIds) { + _logger.finer('Deleting policy: $policyId'); + await cedarDrift.deletePolicy(policyId: policyId); + } + await upsertPolicySet(corePolicySet.merge(differ.newPolicySet)); + }); + } + + /// Upserts a Cedar [PolicySet] into the database. + /// + /// Returns the new, effective [PolicySet]. + Future upsertPolicySet(PolicySet policySet) async { + await transaction(() async { + for (final policy in policySet.policies.entries) { + _logger.finer('Upserting policy: ${policy.key}'); + await cedarDrift.upsertPolicy( + id: typeId('pol'), + policyId: policy.key, + policy: policy.value, + enforcementLevel: 1, + ); + } + for (final template in policySet.templates.entries) { + _logger.finer('Upserting policy template: ${template.key}'); + await cedarDrift.upsertPolicyTemplate( + id: typeId('polt'), + templateId: template.key, + template: template.value, + ); + } + for (final link in policySet.templateLinks) { + _logger.finer( + 'Upserting policy template link: ${link.shortDisplayString}', + ); + + await cedarDrift.upsertPolicyTemplateLink( + id: typeId('polk'), + policyId: link.newId, + templateId: link.templateId, + principalType: link.values[SlotId.principal]?.type, + principalId: link.values[SlotId.principal]?.id, + resourceType: link.values[SlotId.resource]?.type, + resourceId: link.values[SlotId.resource]?.id, + enforcementLevel: 1, + ); + } + }); + _effectivePolicySetCache.invalidate(); + } + + /// Creates a new [entity] in the database. + Future createEntity(Entity entity) async { + _logger.finer( + 'Creating entity: ${entity.uid} with parents: ${entity.parents}', + ); + await cedarDrift.createType( + fqn: entity.uid.type, + ); + await cedarDrift.createEntity( + entityType: entity.uid.type, + entityId: entity.uid.id, + attributeJson: entity.attributes, + ); + for (final parent in entity.parents) { + await cedarDrift.createRelationship( + entityType: entity.uid.type, + entityId: entity.uid.id, + parentType: parent.type, + parentId: parent.id, + ); + } + } + + /// Computes the transitive closure for an [AuthorizationRequest]. + Future> computeRequestClosure({ + Component? principal, + Component? resource, + }) async { + if (principal == null && resource == null) { + return coreEntities; + } + final principalUid = principal?.uid; + final resourceUid = resource?.uid; + final (principalClosure, resourceClosure) = await transaction(() async { + final principalClosure = switch (principalUid) { + final principal? => cedarDrift + .getEntityClosure( + type: principal.type, + id: principal.id, + ) + .getSingle(), + _ => Future.value(const []), + }; + final resourceClosure = switch (resourceUid) { + final resource? => cedarDrift + .getEntityClosure( + type: resource.type, + id: resource.id, + ) + .getSingle(), + _ => Future.value(const []), + }; + return (principalClosure, resourceClosure).wait; + }); + final closure = { + ...coreEntities, + for (final entity in principalClosure) entity.uid: entity, + for (final entity in resourceClosure) entity.uid: entity, + }; + _logger.finest(() { + final entityUids = closure.keys.map((uid) => uid.toString()).sorted(); + return 'Computed closure: $entityUids'; + }); + return closure; + } + + /// Creates a new user in the database. + Future createUser({ + required User user, + }) { + _logger.finer('Creating user: ${user.userId}'); + return transaction(() async { + final newUser = await usersDrift.createUser( + userId: user.userId, + givenName: user.givenName, + familyName: user.familyName, + timeZone: user.timeZone, + languageCode: user.languageCode, + ); + for (final email in user.emails) { + await usersDrift.upsertUserEmail( + userId: user.userId, + email: email.email, + isVerified: email.isVerified, + isPrimary: email.isPrimary, + ); + } + for (final phoneNumber in user.phoneNumbers) { + await usersDrift.upsertUserPhoneNumber( + userId: user.userId, + phoneNumber: phoneNumber.phoneNumber, + isVerified: phoneNumber.isVerified, + isPrimary: phoneNumber.isPrimary, + ); + } + for (final role in user.roles) { + await cedarDrift.createRelationship( + entityType: 'Celest::User', + entityId: newUser.first.userId, + parentType: role.type, + parentId: role.id, + ); + } + return newUser.first.copyWith( + emails: user.emails, + phoneNumbers: user.phoneNumbers, + ); + }); + } + + /// Retrieves a user from the database. + Future getUser({required String userId}) { + return transaction(() async { + final user = await usersDrift.getUser(userId: userId).getSingleOrNull(); + if (user == null) { + return null; + } + final (emails, phoneNumbers) = await ( + usersDrift.getUserEmails(userId: userId).get(), + usersDrift.getUserPhoneNumbers(userId: userId).get(), + ).wait; + return user.copyWith( + emails: emails, + phoneNumbers: phoneNumbers, + ); + }); + } + + /// Finds a user by email. + Future findUserByEmail({required String email}) { + return transaction(() async { + final results = await usersDrift.lookupUserByEmail(email: email).get(); + if (results.isEmpty) { + return null; + } + final LookupUserByEmailResult( + users: user, + userEmails: emailData, + ) = results.first; + if (!emailData.isPrimary) { + final formattedResults = results.map((it) { + final Email(:email, isVerified: verified, isPrimary: primary) = + it.userEmails; + return ( + it.users, + (email: email, verified: verified, primary: primary), + ); + }).join(' | '); + context.logger.warning( + 'No user found with primary, verified email "$email"\n' + 'Found users with email: $formattedResults', + ); + return null; + } + return getUser(userId: user.userId); + }); + } + + /// Finds a user by phone number. + Future findUserByPhoneNumber({required String phoneNumber}) { + return transaction(() async { + final results = + await usersDrift.lookupUserByPhone(phoneNumber: phoneNumber).get(); + if (results.isEmpty) { + return null; + } + final LookupUserByPhoneResult( + users: user, + userPhoneNumbers: phoneNumberData + ) = results.first; + if (!phoneNumberData.isPrimary) { + final formattedResults = results.map((it) { + final PhoneNumber( + :phoneNumber, + isVerified: verified, + isPrimary: primary + ) = it.userPhoneNumbers; + return ( + it.users, + (phoneNumber: phoneNumber, verified: verified, primary: primary), + ); + }).join(' | '); + context.logger.warning( + 'No user found with primary, verified phone number "$phoneNumber"\n' + 'Found users with phone number: $formattedResults', + ); + return null; + } + return getUser(userId: user.userId); + }); + } +} + +/// Creates a diff of a project's authorization config between two versions. +final class _ProjectAuthDiff extends ResolvedAstVisitorWithArg { + _ProjectAuthDiff(); + + final PolicySetBuilder _oldPolicySet = PolicySetBuilder(); + final PolicySetBuilder _newPolicySet = PolicySetBuilder(); + + late final PolicySet oldPolicySet = _oldPolicySet.build(); + late final PolicySet newPolicySet = _newPolicySet.build(); + + Set get removedPolicyIds { + final oldPolicyIds = oldPolicySet.policies.keys.toSet(); + final newPolicyIds = newPolicySet.policies.keys.toSet(); + return oldPolicyIds.difference(newPolicyIds); + } + + Set get removedTemplateIds { + final oldTemplateIds = oldPolicySet.templates.keys.toSet(); + final newTemplateIds = newPolicySet.templates.keys.toSet(); + return oldTemplateIds.difference(newTemplateIds); + } + + Set get removedTemplateLinks { + final oldTemplateLinks = + oldPolicySet.templateLinks.map((link) => link.newId).toSet(); + final newTemplateLinks = + newPolicySet.templateLinks.map((link) => link.newId).toSet(); + return oldTemplateLinks.difference(newTemplateLinks); + } + + @override + void visitProject(ResolvedProject project, ResolvedProject? context) { + for (final api in project.apis.values) { + visitApi(api, context?.apis[api.apiId]); + } + } + + @override + void visitApi(ResolvedApi api, ResolvedApi? context) { + if (api.policySet case final policySet?) { + _newPolicySet + ..policies.addAll(policySet.policies.toMap()) + ..templates.addAll(policySet.templates.toMap()) + ..templateLinks.addAll(policySet.templateLinks); + } + if (context?.policySet case final policySet?) { + _oldPolicySet + ..policies.addAll(policySet.policies.toMap()) + ..templates.addAll(policySet.templates.toMap()) + ..templateLinks.addAll(policySet.templateLinks); + } + for (final function in api.functions.values) { + visitFunction(function, context?.functions[function.functionId]); + } + } + + @override + void visitFunction( + ResolvedCloudFunction function, + ResolvedCloudFunction? context, + ) { + if (function.policySet case final policySet?) { + _newPolicySet + ..policies.addAll(policySet.policies.toMap()) + ..templates.addAll(policySet.templates.toMap()) + ..templateLinks.addAll(policySet.templateLinks); + } + if (context?.policySet case final policySet?) { + _oldPolicySet + ..policies.addAll(policySet.policies.toMap()) + ..templates.addAll(policySet.templates.toMap()) + ..templateLinks.addAll(policySet.templateLinks); + } + } + + @override + void visitAuth(ResolvedAuth auth, covariant Node context) {} + + @override + void visitAuthProvider( + ResolvedAuthProvider provider, + covariant Node context, + ) {} + + @override + void visitDatabase(ResolvedDatabase database, covariant Node context) {} + + @override + void visitDatabaseSchema( + ResolvedDatabaseSchema schema, + covariant Node context, + ) {} + + @override + void visitExternalAuthProvider( + ResolvedExternalAuthProvider provider, + covariant Node context, + ) {} + + @override + void visitSecret(ResolvedSecret secret, covariant Node context) {} + + @override + void visitVariable(ResolvedVariable variable, covariant Node context) {} +} + +extension TemplateLinkDisplayString on TemplateLink { + String get shortDisplayString { + final buf = StringBuffer('('); + buf.write('id=$newId, '); + buf.write('templateId=$templateId, '); + for (final (index, value) in values.entries.indexed) { + buf.write('${value.key.name}=${value.value}'); + if (index < values.length - 1) { + buf.write(', '); + } + } + buf.write(')'); + return buf.toString(); + } +} diff --git a/services/celest_cloud_auth/lib/src/database/auth_database.drift.dart b/services/celest_cloud_auth/lib/src/database/auth_database.drift.dart new file mode 100644 index 00000000..ab4ccf11 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/auth_database.drift.dart @@ -0,0 +1,370 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_cloud_auth/src/database/schema/users.drift.dart' as i1; +import 'package:celest_cloud_auth/src/database/schema/cedar.drift.dart' as i2; +import 'package:celest_cloud_auth/src/database/schema/projects.drift.dart' + as i3; +import 'package:celest_cloud_auth/src/database/schema/auth.drift.dart' as i4; +import 'package:drift/internal/modular.dart' as i5; + +abstract class $AuthDatabase extends i0.GeneratedDatabase { + $AuthDatabase(i0.QueryExecutor e) : super(e); + $AuthDatabaseManager get managers => $AuthDatabaseManager(this); + late final i1.Users users = i1.Users(this); + late final i2.CedarTypes cedarTypes = i2.CedarTypes(this); + late final i2.CedarEntities cedarEntities = i2.CedarEntities(this); + late final i2.CedarRelationships cedarRelationships = + i2.CedarRelationships(this); + late final i1.UserEmails userEmails = i1.UserEmails(this); + late final i1.UserPhoneNumbers userPhoneNumbers = i1.UserPhoneNumbers(this); + late final i3.CelestProjects celestProjects = i3.CelestProjects(this); + late final i3.CelestApis celestApis = i3.CelestApis(this); + late final i3.CelestFunctions celestFunctions = i3.CelestFunctions(this); + late final i2.CedarPolicies cedarPolicies = i2.CedarPolicies(this); + late final i2.CedarPolicyTemplates cedarPolicyTemplates = + i2.CedarPolicyTemplates(this); + late final i2.CedarPolicyTemplateLinks cedarPolicyTemplateLinks = + i2.CedarPolicyTemplateLinks(this); + late final i2.CedarAuthorizationLogs cedarAuthorizationLogs = + i2.CedarAuthorizationLogs(this); + late final i4.CryptoKeys cryptoKeys = i4.CryptoKeys(this); + late final i4.Sessions sessions = i4.Sessions(this); + late final i4.OtpCodes otpCodes = i4.OtpCodes(this); + late final i4.Corks corks = i4.Corks(this); + i4.AuthDrift get authDrift => + i5.ReadDatabaseContainer(this).accessor(i4.AuthDrift.new); + i2.CedarDrift get cedarDrift => + i5.ReadDatabaseContainer(this).accessor(i2.CedarDrift.new); + i3.ProjectsDrift get projectsDrift => i5.ReadDatabaseContainer(this) + .accessor(i3.ProjectsDrift.new); + i1.UsersDrift get usersDrift => + i5.ReadDatabaseContainer(this).accessor(i1.UsersDrift.new); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + users, + cedarTypes, + cedarEntities, + i1.usersCreate, + cedarRelationships, + i1.usersDelete, + userEmails, + userPhoneNumbers, + celestProjects, + celestApis, + i3.celestApisProjectIdx, + i3.celestApisTriggerCreate, + i3.celestApisTriggerDelete, + celestFunctions, + i3.celestFunctionsApiIdx, + i3.celestFunctionsTriggerCreate, + i3.celestFunctionsTriggerDelete, + i2.cedarRelationshipsFkEntityIdx, + i2.cedarRelationshipsFkParentIdx, + cedarPolicies, + cedarPolicyTemplates, + cedarPolicyTemplateLinks, + i2.cedarPolicyTemplateLinksFkTemplateIdIdx, + i2.cedarPolicyTemplateLinksFkPrincipalIdx, + i2.cedarPolicyTemplateLinksFkResourceIdx, + cedarAuthorizationLogs, + cryptoKeys, + i4.cryptoKeysExternalCryptoKeyIdIdx, + sessions, + i4.sessionsUserIdx, + i4.sessionsCryptoKeyIdx, + i4.sessionsExternalSessionIdIdx, + i4.authSessionsUpdateTime, + otpCodes, + i4.otpCodesSessionIdx, + corks, + i4.corksCryptoKeyIdx, + i4.corksBearerIdx, + i4.corksAudienceIdx, + i4.corksIssuerIdx + ]; + @override + i0.StreamQueryUpdateRules get streamUpdateRules => + const i0.StreamQueryUpdateRules( + [ + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.insert), + result: [ + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.insert), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_relationships', kind: i0.UpdateKind.delete), + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('user_emails', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('user_emails', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('user_phone_numbers', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('user_phone_numbers', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_projects', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('celest_apis', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_projects', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('celest_apis', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_apis', + limitUpdateKind: i0.UpdateKind.insert), + result: [ + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.insert), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_apis', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_relationships', kind: i0.UpdateKind.delete), + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_apis', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('celest_functions', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_apis', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('celest_functions', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_functions', + limitUpdateKind: i0.UpdateKind.insert), + result: [ + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.insert), + i0.TableUpdate('cedar_relationships', kind: i0.UpdateKind.insert), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('celest_functions', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_relationships', kind: i0.UpdateKind.delete), + i0.TableUpdate('cedar_entities', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_policy_templates', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_policy_template_links', + kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_policy_templates', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('cedar_policy_template_links', + kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_policy_template_links', + kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('cedar_policy_template_links', + kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('sessions', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('users', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('sessions', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('crypto_keys', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('sessions', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('crypto_keys', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('sessions', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('sessions', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('sessions', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('sessions', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('otp_codes', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('sessions', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('otp_codes', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('crypto_keys', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('crypto_keys', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.delete), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName('cedar_entities', + limitUpdateKind: i0.UpdateKind.update), + result: [ + i0.TableUpdate('corks', kind: i0.UpdateKind.update), + ], + ), + ], + ); +} + +class $AuthDatabaseManager { + final $AuthDatabase _db; + $AuthDatabaseManager(this._db); + i1.$UsersTableManager get users => i1.$UsersTableManager(_db, _db.users); + i2.$CedarTypesTableManager get cedarTypes => + i2.$CedarTypesTableManager(_db, _db.cedarTypes); + i2.$CedarEntitiesTableManager get cedarEntities => + i2.$CedarEntitiesTableManager(_db, _db.cedarEntities); + i2.$CedarRelationshipsTableManager get cedarRelationships => + i2.$CedarRelationshipsTableManager(_db, _db.cedarRelationships); + i1.$UserEmailsTableManager get userEmails => + i1.$UserEmailsTableManager(_db, _db.userEmails); + i1.$UserPhoneNumbersTableManager get userPhoneNumbers => + i1.$UserPhoneNumbersTableManager(_db, _db.userPhoneNumbers); + i3.$CelestProjectsTableManager get celestProjects => + i3.$CelestProjectsTableManager(_db, _db.celestProjects); + i3.$CelestApisTableManager get celestApis => + i3.$CelestApisTableManager(_db, _db.celestApis); + i3.$CelestFunctionsTableManager get celestFunctions => + i3.$CelestFunctionsTableManager(_db, _db.celestFunctions); + i2.$CedarPoliciesTableManager get cedarPolicies => + i2.$CedarPoliciesTableManager(_db, _db.cedarPolicies); + i2.$CedarPolicyTemplatesTableManager get cedarPolicyTemplates => + i2.$CedarPolicyTemplatesTableManager(_db, _db.cedarPolicyTemplates); + i2.$CedarPolicyTemplateLinksTableManager get cedarPolicyTemplateLinks => i2 + .$CedarPolicyTemplateLinksTableManager(_db, _db.cedarPolicyTemplateLinks); + i2.$CedarAuthorizationLogsTableManager get cedarAuthorizationLogs => + i2.$CedarAuthorizationLogsTableManager(_db, _db.cedarAuthorizationLogs); + i4.$CryptoKeysTableManager get cryptoKeys => + i4.$CryptoKeysTableManager(_db, _db.cryptoKeys); + i4.$SessionsTableManager get sessions => + i4.$SessionsTableManager(_db, _db.sessions); + i4.$OtpCodesTableManager get otpCodes => + i4.$OtpCodesTableManager(_db, _db.otpCodes); + i4.$CorksTableManager get corks => i4.$CorksTableManager(_db, _db.corks); +} diff --git a/services/celest_cloud_auth/lib/src/database/database_model.dart b/services/celest_cloud_auth/lib/src/database/database_model.dart new file mode 100644 index 00000000..9d50dacc --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/database_model.dart @@ -0,0 +1,39 @@ +import 'package:drift/drift.dart'; + +final class TimestampType implements CustomSqlType { + const TimestampType(); + + @override + String mapToSqlLiteral(DateTime dartValue) { + final seconds = dartValue.millisecondsSinceEpoch / 1000; + return seconds.toStringAsFixed(3); + } + + @override + Object mapToSqlParameter(DateTime dartValue) { + return dartValue.millisecondsSinceEpoch / 1000; + } + + @override + DateTime read(Object fromSql) { + if (fromSql is! num) { + throw ArgumentError.value( + fromSql, + 'fromSql', + 'Expected a number, got a ${fromSql.runtimeType}', + ); + } + + final seconds = fromSql.toInt(); + final milliseconds = ((fromSql - seconds) * 1000).toInt(); + return DateTime.fromMillisecondsSinceEpoch( + seconds * 1000 + milliseconds, + isUtc: true, + ); + } + + @override + String sqlTypeName(GenerationContext context) { + return 'DATETIME'; + } +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/auth.drift b/services/celest_cloud_auth/lib/src/database/schema/auth.drift new file mode 100644 index 00000000..22c0ce5d --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/auth.drift @@ -0,0 +1,376 @@ +import 'cedar.drift'; +import 'users.drift'; + +import 'schema_imports.dart'; + +-- Stores crypto key metadata +CREATE TABLE IF NOT EXISTS crypto_keys ( + -- The unique identifier of the key. + crypto_key_id BLOB NOT NULL PRIMARY KEY, + + -- The key's purpose. + key_purpose TEXT NOT NULL, + + -- The key's algorithm. + key_algorithm TEXT NOT NULL, + + -- The key material. + -- + -- Either `key_material` or `external_crypto_key_id` must be set. + key_material BLOB, + + -- The key's external ID, if managed outside of Celest. + -- + -- Either `key_material` or `external_crypto_key_id` must be set. + external_crypto_key_id TEXT UNIQUE, + + CHECK (key_material IS NOT NULL OR external_crypto_key_id IS NOT NULL) +) WITH CryptoKey; + +CREATE INDEX IF NOT EXISTS crypto_keys_external_crypto_key_id_idx ON crypto_keys(external_crypto_key_id); + +-- Stores ongoing authentication sessions. +CREATE TABLE IF NOT EXISTS sessions ( + -- Explicit rowid for codegen. + rowid INTEGER PRIMARY KEY AUTOINCREMENT, + + -- The unique identifier of the session. + -- + -- Format: sess_ + session_id TEXT NOT NULL UNIQUE, + + -- The ID of the HMAC key for the session, which signs the continuation tokens + -- and mints HOTP codes. + -- + -- RFC4226 recommends 160 bits of entropy for HMAC-SHA1. + crypto_key_id BLOB NOT NULL, + + -- The ID of the user who started the session. + user_id TEXT, + + -- Client information for the session. + client_info BLOB NOT NULL MAPPED BY `const SessionClientConverter()`, + + -- The authentication factor used to start the session. + authentication_factor BLOB NOT NULL MAPPED BY `const AuthenticationFactorConverter()`, + + -- The state of the session. + -- + -- This is used to track the progress of the session. + state BLOB MAPPED BY `const SessionStateConverter()`, + + -- The IP address of the client who started the session. + ip_address TEXT, + + -- The ID of the external session, if any. + -- + -- This field can be used to link the HOTP code to a corresponding session in + -- another system. + external_session_id TEXT, + + create_time `const TimestampType()` NOT NULL DEFAULT (unixepoch('now', 'subsec')), + update_time `const TimestampType()`, + expire_time `const TimestampType()` NOT NULL, + cancel_time `const TimestampType()`, + + CONSTRAINT sessions_user_fk FOREIGN KEY (user_id) REFERENCES users(user_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED, + + CONSTRAINT sessions_key_fk FOREIGN KEY (crypto_key_id) REFERENCES crypto_keys(crypto_key_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +) WITH Session; + +-- Index for the session's FK. +CREATE INDEX IF NOT EXISTS sessions_user_idx ON sessions(user_id); +CREATE INDEX IF NOT EXISTS sessions_crypto_key_idx ON sessions(crypto_key_id); +CREATE INDEX IF NOT EXISTS sessions_external_session_id_idx ON sessions(external_session_id); + +-- Trigger to update the update_time when a session is updated. +CREATE TRIGGER IF NOT EXISTS auth_sessions_update_time +AFTER UPDATE ON sessions +BEGIN + UPDATE sessions + SET update_time = unixepoch('now', 'subsec') + WHERE rowid = OLD.rowid; +END; + +-- Stores HOTP parameters for ongoing authentication sessions. +CREATE TABLE IF NOT EXISTS otp_codes ( + -- The row ID which serves as the HOTP counter. + rowid INTEGER PRIMARY KEY AUTOINCREMENT, + + -- The ID of the session. + session_id TEXT NOT NULL UNIQUE, + + -- The counter value for the HOTP code. + resend_attempt INTEGER NOT NULL DEFAULT 0, + + -- The throttling parameter for bruteforce prevention. + verify_attempt INTEGER NOT NULL DEFAULT 0, + + -- The time the HOTP code was last used. + update_time `const TimestampType()` NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + CONSTRAINT otp_codes_session_fk FOREIGN KEY (session_id) REFERENCES sessions(session_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Index for the HOTP code's FK. +CREATE INDEX IF NOT EXISTS otp_codes_session_idx ON otp_codes(session_id); + +-- Stores corks for Celest Cloud user sessions. +CREATE TABLE IF NOT EXISTS corks ( + -- The unique identifier for the cork and its root key. + cork_id BLOB NOT NULL PRIMARY KEY, + + -- The ID of the HMAC key used to sign the cork. + -- + -- This is typically derived from the organization's root key. + crypto_key_id BLOB NOT NULL, + + -- The Cedar type of the bearer entity. + -- + -- For example, `Celest::User` or `Celest::ServiceAccount`. + bearer_type TEXT, + + -- The unique identifier of the bearer entity. + bearer_id TEXT, + + -- The Cedar type of the audience entity. + -- + -- For example, `Celest::Service` or `Celest::Api`. + audience_type TEXT, + + -- The unique identifier of the audience entity. + audience_id TEXT, + + -- The Cedar type of the issuer entity. + -- + -- For example, `Celest::Organization` or `Celest::Service`. + issuer_type TEXT, + + -- The unique identifier of the issuer entity. + issuer_id TEXT, + + -- The time the cork was created. + create_time `const TimestampType()` NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + -- The time the cork will expire. + expire_time `const TimestampType()`, + + -- The time the cork was last used. + last_use_time `const TimestampType()`, + + CONSTRAINT corks_crypto_key_fk FOREIGN KEY (crypto_key_id) REFERENCES crypto_keys(crypto_key_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED, + + CONSTRAINT corks_bearer_fk FOREIGN KEY (bearer_type, bearer_id) REFERENCES cedar_entities(entity_type, entity_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED, + + CONSTRAINT corks_audience_fk FOREIGN KEY (audience_type, audience_id) REFERENCES cedar_entities(entity_type, entity_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED, + + CONSTRAINT corks_issuer_fk FOREIGN KEY (issuer_type, issuer_id) REFERENCES cedar_entities(entity_type, entity_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Index for the cork's FK. +CREATE INDEX IF NOT EXISTS corks_crypto_key_idx ON corks(crypto_key_id); +CREATE INDEX IF NOT EXISTS corks_bearer_idx ON corks(bearer_type, bearer_id); +CREATE INDEX IF NOT EXISTS corks_audience_idx ON corks(audience_type, audience_id); +CREATE INDEX IF NOT EXISTS corks_issuer_idx ON corks(issuer_type, issuer_id); + +-- Creates a new crypto key. +createCryptoKey: + INSERT INTO crypto_keys( + crypto_key_id, + key_purpose, + key_algorithm, + key_material, + external_crypto_key_id + ) + VALUES ( + :crypto_key_id, + :key_purpose, + :key_algorithm, + :key_material, + :external_crypto_key_id + ) + ON CONFLICT DO NOTHING + RETURNING *; + +-- Retrieves the crypto key for the given ID. +getCryptoKey: + SELECT * FROM crypto_keys + WHERE crypto_key_id = :crypto_key_id; + +-- Retrieves the external crypto key for the given ID. +getExternalCryptoKey: + SELECT * FROM crypto_keys + WHERE external_crypto_key_id = :external_crypto_key_id; + +-- Finds the crypto key for the given purpose. +findCryptoKey: + SELECT * FROM crypto_keys + WHERE key_purpose = :key_purpose + AND key_algorithm = :key_algorithm; + +-- Deletes the crypto key for the given ID. +deleteCryptoKey: + DELETE FROM crypto_keys + WHERE crypto_key_id = :crypto_key_id; + +-- Creates a new auth session. +createSession: + INSERT INTO sessions( + session_id, + crypto_key_id, + user_id, + client_info, + authentication_factor, + state, + ip_address, + expire_time, + cancel_time, + external_session_id + ) + VALUES ( + :session_id, + :crypto_key_id, + :user_id, + :client_info, + :authentication_factor, + :state, + :ip_address, + :expire_time, + :cancel_time, + :external_session_id + ) + RETURNING *; + +-- Retrieves the auth session for the given ID. +getSession: + SELECT * FROM sessions + WHERE + ( + sessions.session_id = :session_id + OR sessions.external_session_id = :session_id + ) + AND sessions.expire_time > unixepoch('now', 'subsec') + AND sessions.cancel_time IS NULL; + +-- Updates the state of the auth session for the given ID. +updateSession: + UPDATE sessions + SET state = :state + WHERE + session_id = :session_id + OR external_session_id = :session_id + RETURNING *; + +-- Deletes the auth session for the given ID. +deleteSession: + DELETE FROM sessions + WHERE + session_id = :session_id + OR external_session_id = :session_id; + +-- Cancels the auth session for the given ID. +cancelSession: + UPDATE sessions + SET cancel_time = unixepoch('now', 'subsec') + WHERE + session_id = :session_id + OR external_session_id = :session_id; + +-- Creates a cork in the database. +createCork: + INSERT INTO corks( + cork_id, + crypto_key_id, + bearer_type, + bearer_id, + audience_type, + audience_id, + issuer_type, + issuer_id, + expire_time + ) + VALUES ( + :cork_id, + :crypto_key_id, + :bearer_type, + :bearer_id, + :audience_type, + :audience_id, + :issuer_type, + :issuer_id, + :expire_time + ); + +-- Retrieves the cork for the given ID. +getCork: + SELECT * FROM corks + WHERE cork_id = :cork_id; + +-- Deletes the cork for the given ID. +deleteCork: + DELETE FROM corks + WHERE cork_id = :cork_id; + +-- Revokes all corks under the given entity. +deleteCorksForEntity( + :bearer_type AS TEXT OR NULL, + :bearer_id AS TEXT OR NULL, + :audience_type AS TEXT OR NULL, + :audience_id AS TEXT OR NULL, + :issuer_type AS TEXT OR NULL, + :issuer_id AS TEXT OR NULL +): + DELETE FROM corks + WHERE + (:bearer_type IS NULL OR bearer_type = :bearer_type) + AND (:bearer_id IS NULL OR bearer_id = :bearer_id) + AND (:audience_type IS NULL OR audience_type = :audience_type) + AND (:audience_id IS NULL OR audience_id = :audience_id) + AND (:issuer_type IS NULL OR issuer_type = :issuer_type) + AND (:issuer_id IS NULL OR issuer_id = :issuer_id) + RETURNING cork_id; + +-- Creates a new HOTP code for the given session. +upsertOtpCode( + :session_id, + :resend_attempt AS INTEGER OR NULL, + :verify_attempt AS INTEGER OR NULL +): + INSERT INTO otp_codes(session_id, resend_attempt, verify_attempt) + VALUES (:session_id, coalesce(:resend_attempt, 0), coalesce(:verify_attempt, 0)) + ON CONFLICT(session_id) DO UPDATE + SET + resend_attempt = coalesce(:resend_attempt, resend_attempt), + verify_attempt = coalesce(:verify_attempt, verify_attempt) + RETURNING *; + +-- Updates the HOTP code for the given session. +updateOtpCode( + :resend_attempt AS INTEGER OR NULL, + :verify_attempt AS INTEGER OR NULL +): + UPDATE otp_codes + SET + resend_attempt = coalesce(:resend_attempt, resend_attempt), + verify_attempt = coalesce(:verify_attempt, verify_attempt) + WHERE session_id = :session_id + RETURNING *; + +-- Retrieves the HOTP code for the given session. +getOtpCode: + SELECT * FROM otp_codes + WHERE session_id = :session_id; diff --git a/services/celest_cloud_auth/lib/src/database/schema/auth.drift.dart b/services/celest_cloud_auth/lib/src/database/schema/auth.drift.dart new file mode 100644 index 00000000..6822d718 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/auth.drift.dart @@ -0,0 +1,2418 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_cloud_auth/src/crypto/crypto_key_model.dart' as i1; +import 'dart:typed_data' as i2; +import 'package:celest_cloud_auth/src/database/schema/auth.drift.dart' as i3; +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart' + as i4; +import 'package:celest_cloud_auth/src/database/database_model.dart' as i5; +import 'package:celest_cloud_auth/src/database/schema/converters/auth_converters.dart' + as i6; +import 'package:drift/internal/modular.dart' as i7; +import 'dart:async' as i8; + +class CryptoKeys extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CryptoKeys(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn cryptoKeyId = + i0.GeneratedColumn('crypto_key_id', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn keyPurpose = i0.GeneratedColumn( + 'key_purpose', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn keyAlgorithm = + i0.GeneratedColumn('key_algorithm', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn keyMaterial = + i0.GeneratedColumn('key_material', aliasedName, true, + type: i0.DriftSqlType.blob, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn externalCryptoKeyId = + i0.GeneratedColumn('external_crypto_key_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'UNIQUE'); + @override + List get $columns => + [cryptoKeyId, keyPurpose, keyAlgorithm, keyMaterial, externalCryptoKeyId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'crypto_keys'; + @override + Set get $primaryKey => {cryptoKeyId}; + @override + i1.CryptoKey map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CryptoKey( + cryptoKeyId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}crypto_key_id'])!, + keyPurpose: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}key_purpose'])!, + keyAlgorithm: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}key_algorithm'])!, + keyMaterial: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}key_material']), + externalCryptoKeyId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}external_crypto_key_id']), + ); + } + + @override + CryptoKeys createAlias(String alias) { + return CryptoKeys(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'CHECK(key_material IS NOT NULL OR external_crypto_key_id IS NOT NULL)' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CryptoKeysCompanion extends i0.UpdateCompanion { + final i0.Value cryptoKeyId; + final i0.Value keyPurpose; + final i0.Value keyAlgorithm; + final i0.Value keyMaterial; + final i0.Value externalCryptoKeyId; + final i0.Value rowid; + const CryptoKeysCompanion({ + this.cryptoKeyId = const i0.Value.absent(), + this.keyPurpose = const i0.Value.absent(), + this.keyAlgorithm = const i0.Value.absent(), + this.keyMaterial = const i0.Value.absent(), + this.externalCryptoKeyId = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CryptoKeysCompanion.insert({ + required i2.Uint8List cryptoKeyId, + required String keyPurpose, + required String keyAlgorithm, + this.keyMaterial = const i0.Value.absent(), + this.externalCryptoKeyId = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : cryptoKeyId = i0.Value(cryptoKeyId), + keyPurpose = i0.Value(keyPurpose), + keyAlgorithm = i0.Value(keyAlgorithm); + static i0.Insertable custom({ + i0.Expression? cryptoKeyId, + i0.Expression? keyPurpose, + i0.Expression? keyAlgorithm, + i0.Expression? keyMaterial, + i0.Expression? externalCryptoKeyId, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (cryptoKeyId != null) 'crypto_key_id': cryptoKeyId, + if (keyPurpose != null) 'key_purpose': keyPurpose, + if (keyAlgorithm != null) 'key_algorithm': keyAlgorithm, + if (keyMaterial != null) 'key_material': keyMaterial, + if (externalCryptoKeyId != null) + 'external_crypto_key_id': externalCryptoKeyId, + if (rowid != null) 'rowid': rowid, + }); + } + + i3.CryptoKeysCompanion copyWith( + {i0.Value? cryptoKeyId, + i0.Value? keyPurpose, + i0.Value? keyAlgorithm, + i0.Value? keyMaterial, + i0.Value? externalCryptoKeyId, + i0.Value? rowid}) { + return i3.CryptoKeysCompanion( + cryptoKeyId: cryptoKeyId ?? this.cryptoKeyId, + keyPurpose: keyPurpose ?? this.keyPurpose, + keyAlgorithm: keyAlgorithm ?? this.keyAlgorithm, + keyMaterial: keyMaterial ?? this.keyMaterial, + externalCryptoKeyId: externalCryptoKeyId ?? this.externalCryptoKeyId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (cryptoKeyId.present) { + map['crypto_key_id'] = i0.Variable(cryptoKeyId.value); + } + if (keyPurpose.present) { + map['key_purpose'] = i0.Variable(keyPurpose.value); + } + if (keyAlgorithm.present) { + map['key_algorithm'] = i0.Variable(keyAlgorithm.value); + } + if (keyMaterial.present) { + map['key_material'] = i0.Variable(keyMaterial.value); + } + if (externalCryptoKeyId.present) { + map['external_crypto_key_id'] = + i0.Variable(externalCryptoKeyId.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CryptoKeysCompanion(') + ..write('cryptoKeyId: $cryptoKeyId, ') + ..write('keyPurpose: $keyPurpose, ') + ..write('keyAlgorithm: $keyAlgorithm, ') + ..write('keyMaterial: $keyMaterial, ') + ..write('externalCryptoKeyId: $externalCryptoKeyId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CryptoKeysCreateCompanionBuilder = i3.CryptoKeysCompanion Function({ + required i2.Uint8List cryptoKeyId, + required String keyPurpose, + required String keyAlgorithm, + i0.Value keyMaterial, + i0.Value externalCryptoKeyId, + i0.Value rowid, +}); +typedef $CryptoKeysUpdateCompanionBuilder = i3.CryptoKeysCompanion Function({ + i0.Value cryptoKeyId, + i0.Value keyPurpose, + i0.Value keyAlgorithm, + i0.Value keyMaterial, + i0.Value externalCryptoKeyId, + i0.Value rowid, +}); + +class $CryptoKeysFilterComposer + extends i0.FilterComposer { + $CryptoKeysFilterComposer(super.$state); + i0.ColumnFilters get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get keyPurpose => $state.composableBuilder( + column: $state.table.keyPurpose, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get keyAlgorithm => $state.composableBuilder( + column: $state.table.keyAlgorithm, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get keyMaterial => $state.composableBuilder( + column: $state.table.keyMaterial, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get externalCryptoKeyId => $state.composableBuilder( + column: $state.table.externalCryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CryptoKeysOrderingComposer + extends i0.OrderingComposer { + $CryptoKeysOrderingComposer(super.$state); + i0.ColumnOrderings get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get keyPurpose => $state.composableBuilder( + column: $state.table.keyPurpose, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get keyAlgorithm => $state.composableBuilder( + column: $state.table.keyAlgorithm, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get keyMaterial => $state.composableBuilder( + column: $state.table.keyMaterial, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get externalCryptoKeyId => + $state.composableBuilder( + column: $state.table.externalCryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CryptoKeysTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i3.CryptoKeys, + i1.CryptoKey, + i3.$CryptoKeysFilterComposer, + i3.$CryptoKeysOrderingComposer, + $CryptoKeysCreateCompanionBuilder, + $CryptoKeysUpdateCompanionBuilder, + ( + i1.CryptoKey, + i0.BaseReferences + ), + i1.CryptoKey, + i0.PrefetchHooks Function()> { + $CryptoKeysTableManager(i0.GeneratedDatabase db, i3.CryptoKeys table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i3.$CryptoKeysFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i3.$CryptoKeysOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value cryptoKeyId = const i0.Value.absent(), + i0.Value keyPurpose = const i0.Value.absent(), + i0.Value keyAlgorithm = const i0.Value.absent(), + i0.Value keyMaterial = const i0.Value.absent(), + i0.Value externalCryptoKeyId = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i3.CryptoKeysCompanion( + cryptoKeyId: cryptoKeyId, + keyPurpose: keyPurpose, + keyAlgorithm: keyAlgorithm, + keyMaterial: keyMaterial, + externalCryptoKeyId: externalCryptoKeyId, + rowid: rowid, + ), + createCompanionCallback: ({ + required i2.Uint8List cryptoKeyId, + required String keyPurpose, + required String keyAlgorithm, + i0.Value keyMaterial = const i0.Value.absent(), + i0.Value externalCryptoKeyId = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i3.CryptoKeysCompanion.insert( + cryptoKeyId: cryptoKeyId, + keyPurpose: keyPurpose, + keyAlgorithm: keyAlgorithm, + keyMaterial: keyMaterial, + externalCryptoKeyId: externalCryptoKeyId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CryptoKeysProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i3.CryptoKeys, + i1.CryptoKey, + i3.$CryptoKeysFilterComposer, + i3.$CryptoKeysOrderingComposer, + $CryptoKeysCreateCompanionBuilder, + $CryptoKeysUpdateCompanionBuilder, + ( + i1.CryptoKey, + i0.BaseReferences + ), + i1.CryptoKey, + i0.PrefetchHooks Function()>; +i0.Index get cryptoKeysExternalCryptoKeyIdIdx => i0.Index( + 'crypto_keys_external_crypto_key_id_idx', + 'CREATE INDEX IF NOT EXISTS crypto_keys_external_crypto_key_id_idx ON crypto_keys (external_crypto_key_id)'); + +class Sessions extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Sessions(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn rowid = i0.GeneratedColumn( + 'rowid', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT'); + late final i0.GeneratedColumn sessionId = i0.GeneratedColumn( + 'session_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL UNIQUE'); + late final i0.GeneratedColumn cryptoKeyId = + i0.GeneratedColumn('crypto_key_id', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumnWithTypeConverter + clientInfo = i0.GeneratedColumn( + 'client_info', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(i3.Sessions.$converterclientInfo); + late final i0 + .GeneratedColumnWithTypeConverter + authenticationFactor = i0.GeneratedColumn( + 'authentication_factor', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter( + i3.Sessions.$converterauthenticationFactor); + late final i0.GeneratedColumnWithTypeConverter + state = i0.GeneratedColumn('state', aliasedName, true, + type: i0.DriftSqlType.blob, + requiredDuringInsert: false, + $customConstraints: '') + .withConverter(i3.Sessions.$converterstaten); + late final i0.GeneratedColumn ipAddress = i0.GeneratedColumn( + 'ip_address', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn externalSessionId = + i0.GeneratedColumn('external_session_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn createTime = + i0.GeneratedColumn( + 'create_time', aliasedName, false, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn updateTime = + i0.GeneratedColumn('update_time', aliasedName, true, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn expireTime = + i0.GeneratedColumn('expire_time', aliasedName, false, + type: const i5.TimestampType(), + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn cancelTime = + i0.GeneratedColumn('cancel_time', aliasedName, true, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: ''); + @override + List get $columns => [ + rowid, + sessionId, + cryptoKeyId, + userId, + clientInfo, + authenticationFactor, + state, + ipAddress, + externalSessionId, + createTime, + updateTime, + expireTime, + cancelTime + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'sessions'; + @override + Set get $primaryKey => {rowid}; + @override + i4.Session map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i4.Session( + sessionId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}session_id'])!, + cryptoKeyId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}crypto_key_id'])!, + userId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}user_id']), + expireTime: attachedDatabase.typeMapping.read( + const i5.TimestampType(), data['${effectivePrefix}expire_time'])!, + authenticationFactor: i3.Sessions.$converterauthenticationFactor.fromSql( + attachedDatabase.typeMapping.read(i0.DriftSqlType.blob, + data['${effectivePrefix}authentication_factor'])!), + state: i3.Sessions.$converterstaten.fromSql(attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}state'])), + clientInfo: i3.Sessions.$converterclientInfo.fromSql(attachedDatabase + .typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}client_info'])!), + ipAddress: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}ip_address']), + externalSessionId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}external_session_id']), + ); + } + + @override + Sessions createAlias(String alias) { + return Sessions(attachedDatabase, alias); + } + + static i0.TypeConverter $converterclientInfo = + const i6.SessionClientConverter(); + static i0.TypeConverter + $converterauthenticationFactor = const i6.AuthenticationFactorConverter(); + static i0.TypeConverter $converterstate = + const i6.SessionStateConverter(); + static i0.TypeConverter $converterstaten = + i0.NullAwareTypeConverter.wrap($converterstate); + @override + List get customConstraints => const [ + 'CONSTRAINT sessions_user_fk FOREIGN KEY(user_id)REFERENCES users(user_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED', + 'CONSTRAINT sessions_key_fk FOREIGN KEY(crypto_key_id)REFERENCES crypto_keys(crypto_key_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class SessionsCompanion extends i0.UpdateCompanion { + final i0.Value rowid; + final i0.Value sessionId; + final i0.Value cryptoKeyId; + final i0.Value userId; + final i0.Value clientInfo; + final i0.Value authenticationFactor; + final i0.Value state; + final i0.Value ipAddress; + final i0.Value externalSessionId; + final i0.Value createTime; + final i0.Value updateTime; + final i0.Value expireTime; + final i0.Value cancelTime; + const SessionsCompanion({ + this.rowid = const i0.Value.absent(), + this.sessionId = const i0.Value.absent(), + this.cryptoKeyId = const i0.Value.absent(), + this.userId = const i0.Value.absent(), + this.clientInfo = const i0.Value.absent(), + this.authenticationFactor = const i0.Value.absent(), + this.state = const i0.Value.absent(), + this.ipAddress = const i0.Value.absent(), + this.externalSessionId = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + this.expireTime = const i0.Value.absent(), + this.cancelTime = const i0.Value.absent(), + }); + SessionsCompanion.insert({ + this.rowid = const i0.Value.absent(), + required String sessionId, + required i2.Uint8List cryptoKeyId, + this.userId = const i0.Value.absent(), + required i4.SessionClient clientInfo, + required i4.AuthenticationFactor authenticationFactor, + this.state = const i0.Value.absent(), + this.ipAddress = const i0.Value.absent(), + this.externalSessionId = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + required DateTime expireTime, + this.cancelTime = const i0.Value.absent(), + }) : sessionId = i0.Value(sessionId), + cryptoKeyId = i0.Value(cryptoKeyId), + clientInfo = i0.Value(clientInfo), + authenticationFactor = i0.Value(authenticationFactor), + expireTime = i0.Value(expireTime); + static i0.Insertable custom({ + i0.Expression? rowid, + i0.Expression? sessionId, + i0.Expression? cryptoKeyId, + i0.Expression? userId, + i0.Expression? clientInfo, + i0.Expression? authenticationFactor, + i0.Expression? state, + i0.Expression? ipAddress, + i0.Expression? externalSessionId, + i0.Expression? createTime, + i0.Expression? updateTime, + i0.Expression? expireTime, + i0.Expression? cancelTime, + }) { + return i0.RawValuesInsertable({ + if (rowid != null) 'rowid': rowid, + if (sessionId != null) 'session_id': sessionId, + if (cryptoKeyId != null) 'crypto_key_id': cryptoKeyId, + if (userId != null) 'user_id': userId, + if (clientInfo != null) 'client_info': clientInfo, + if (authenticationFactor != null) + 'authentication_factor': authenticationFactor, + if (state != null) 'state': state, + if (ipAddress != null) 'ip_address': ipAddress, + if (externalSessionId != null) 'external_session_id': externalSessionId, + if (createTime != null) 'create_time': createTime, + if (updateTime != null) 'update_time': updateTime, + if (expireTime != null) 'expire_time': expireTime, + if (cancelTime != null) 'cancel_time': cancelTime, + }); + } + + i3.SessionsCompanion copyWith( + {i0.Value? rowid, + i0.Value? sessionId, + i0.Value? cryptoKeyId, + i0.Value? userId, + i0.Value? clientInfo, + i0.Value? authenticationFactor, + i0.Value? state, + i0.Value? ipAddress, + i0.Value? externalSessionId, + i0.Value? createTime, + i0.Value? updateTime, + i0.Value? expireTime, + i0.Value? cancelTime}) { + return i3.SessionsCompanion( + rowid: rowid ?? this.rowid, + sessionId: sessionId ?? this.sessionId, + cryptoKeyId: cryptoKeyId ?? this.cryptoKeyId, + userId: userId ?? this.userId, + clientInfo: clientInfo ?? this.clientInfo, + authenticationFactor: authenticationFactor ?? this.authenticationFactor, + state: state ?? this.state, + ipAddress: ipAddress ?? this.ipAddress, + externalSessionId: externalSessionId ?? this.externalSessionId, + createTime: createTime ?? this.createTime, + updateTime: updateTime ?? this.updateTime, + expireTime: expireTime ?? this.expireTime, + cancelTime: cancelTime ?? this.cancelTime, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + if (sessionId.present) { + map['session_id'] = i0.Variable(sessionId.value); + } + if (cryptoKeyId.present) { + map['crypto_key_id'] = i0.Variable(cryptoKeyId.value); + } + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (clientInfo.present) { + map['client_info'] = i0.Variable( + i3.Sessions.$converterclientInfo.toSql(clientInfo.value)); + } + if (authenticationFactor.present) { + map['authentication_factor'] = i0.Variable(i3 + .Sessions.$converterauthenticationFactor + .toSql(authenticationFactor.value)); + } + if (state.present) { + map['state'] = i0.Variable( + i3.Sessions.$converterstaten.toSql(state.value)); + } + if (ipAddress.present) { + map['ip_address'] = i0.Variable(ipAddress.value); + } + if (externalSessionId.present) { + map['external_session_id'] = i0.Variable(externalSessionId.value); + } + if (createTime.present) { + map['create_time'] = + i0.Variable(createTime.value, const i5.TimestampType()); + } + if (updateTime.present) { + map['update_time'] = + i0.Variable(updateTime.value, const i5.TimestampType()); + } + if (expireTime.present) { + map['expire_time'] = + i0.Variable(expireTime.value, const i5.TimestampType()); + } + if (cancelTime.present) { + map['cancel_time'] = + i0.Variable(cancelTime.value, const i5.TimestampType()); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SessionsCompanion(') + ..write('rowid: $rowid, ') + ..write('sessionId: $sessionId, ') + ..write('cryptoKeyId: $cryptoKeyId, ') + ..write('userId: $userId, ') + ..write('clientInfo: $clientInfo, ') + ..write('authenticationFactor: $authenticationFactor, ') + ..write('state: $state, ') + ..write('ipAddress: $ipAddress, ') + ..write('externalSessionId: $externalSessionId, ') + ..write('createTime: $createTime, ') + ..write('updateTime: $updateTime, ') + ..write('expireTime: $expireTime, ') + ..write('cancelTime: $cancelTime') + ..write(')')) + .toString(); + } +} + +typedef $SessionsCreateCompanionBuilder = i3.SessionsCompanion Function({ + i0.Value rowid, + required String sessionId, + required i2.Uint8List cryptoKeyId, + i0.Value userId, + required i4.SessionClient clientInfo, + required i4.AuthenticationFactor authenticationFactor, + i0.Value state, + i0.Value ipAddress, + i0.Value externalSessionId, + i0.Value createTime, + i0.Value updateTime, + required DateTime expireTime, + i0.Value cancelTime, +}); +typedef $SessionsUpdateCompanionBuilder = i3.SessionsCompanion Function({ + i0.Value rowid, + i0.Value sessionId, + i0.Value cryptoKeyId, + i0.Value userId, + i0.Value clientInfo, + i0.Value authenticationFactor, + i0.Value state, + i0.Value ipAddress, + i0.Value externalSessionId, + i0.Value createTime, + i0.Value updateTime, + i0.Value expireTime, + i0.Value cancelTime, +}); + +class $SessionsFilterComposer + extends i0.FilterComposer { + $SessionsFilterComposer(super.$state); + i0.ColumnFilters get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get sessionId => $state.composableBuilder( + column: $state.table.sessionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get clientInfo => $state.composableBuilder( + column: $state.table.clientInfo, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get authenticationFactor => $state.composableBuilder( + column: $state.table.authenticationFactor, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get state => $state.composableBuilder( + column: $state.table.state, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get ipAddress => $state.composableBuilder( + column: $state.table.ipAddress, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get externalSessionId => $state.composableBuilder( + column: $state.table.externalSessionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get cancelTime => $state.composableBuilder( + column: $state.table.cancelTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $SessionsOrderingComposer + extends i0.OrderingComposer { + $SessionsOrderingComposer(super.$state); + i0.ColumnOrderings get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get sessionId => $state.composableBuilder( + column: $state.table.sessionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get clientInfo => $state.composableBuilder( + column: $state.table.clientInfo, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get authenticationFactor => + $state.composableBuilder( + column: $state.table.authenticationFactor, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get state => $state.composableBuilder( + column: $state.table.state, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get ipAddress => $state.composableBuilder( + column: $state.table.ipAddress, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get externalSessionId => $state.composableBuilder( + column: $state.table.externalSessionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get cancelTime => $state.composableBuilder( + column: $state.table.cancelTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $SessionsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i3.Sessions, + i4.Session, + i3.$SessionsFilterComposer, + i3.$SessionsOrderingComposer, + $SessionsCreateCompanionBuilder, + $SessionsUpdateCompanionBuilder, + ( + i4.Session, + i0.BaseReferences + ), + i4.Session, + i0.PrefetchHooks Function()> { + $SessionsTableManager(i0.GeneratedDatabase db, i3.Sessions table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i3.$SessionsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i3.$SessionsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + i0.Value sessionId = const i0.Value.absent(), + i0.Value cryptoKeyId = const i0.Value.absent(), + i0.Value userId = const i0.Value.absent(), + i0.Value clientInfo = const i0.Value.absent(), + i0.Value authenticationFactor = + const i0.Value.absent(), + i0.Value state = const i0.Value.absent(), + i0.Value ipAddress = const i0.Value.absent(), + i0.Value externalSessionId = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + i0.Value expireTime = const i0.Value.absent(), + i0.Value cancelTime = const i0.Value.absent(), + }) => + i3.SessionsCompanion( + rowid: rowid, + sessionId: sessionId, + cryptoKeyId: cryptoKeyId, + userId: userId, + clientInfo: clientInfo, + authenticationFactor: authenticationFactor, + state: state, + ipAddress: ipAddress, + externalSessionId: externalSessionId, + createTime: createTime, + updateTime: updateTime, + expireTime: expireTime, + cancelTime: cancelTime, + ), + createCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + required String sessionId, + required i2.Uint8List cryptoKeyId, + i0.Value userId = const i0.Value.absent(), + required i4.SessionClient clientInfo, + required i4.AuthenticationFactor authenticationFactor, + i0.Value state = const i0.Value.absent(), + i0.Value ipAddress = const i0.Value.absent(), + i0.Value externalSessionId = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + required DateTime expireTime, + i0.Value cancelTime = const i0.Value.absent(), + }) => + i3.SessionsCompanion.insert( + rowid: rowid, + sessionId: sessionId, + cryptoKeyId: cryptoKeyId, + userId: userId, + clientInfo: clientInfo, + authenticationFactor: authenticationFactor, + state: state, + ipAddress: ipAddress, + externalSessionId: externalSessionId, + createTime: createTime, + updateTime: updateTime, + expireTime: expireTime, + cancelTime: cancelTime, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $SessionsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i3.Sessions, + i4.Session, + i3.$SessionsFilterComposer, + i3.$SessionsOrderingComposer, + $SessionsCreateCompanionBuilder, + $SessionsUpdateCompanionBuilder, + ( + i4.Session, + i0.BaseReferences + ), + i4.Session, + i0.PrefetchHooks Function()>; +i0.Index get sessionsUserIdx => i0.Index('sessions_user_idx', + 'CREATE INDEX IF NOT EXISTS sessions_user_idx ON sessions (user_id)'); +i0.Index get sessionsCryptoKeyIdx => i0.Index('sessions_crypto_key_idx', + 'CREATE INDEX IF NOT EXISTS sessions_crypto_key_idx ON sessions (crypto_key_id)'); +i0.Index get sessionsExternalSessionIdIdx => i0.Index( + 'sessions_external_session_id_idx', + 'CREATE INDEX IF NOT EXISTS sessions_external_session_id_idx ON sessions (external_session_id)'); +i0.Trigger get authSessionsUpdateTime => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS auth_sessions_update_time AFTER UPDATE ON sessions BEGIN UPDATE sessions SET update_time = unixepoch(\'now\', \'subsec\') WHERE "rowid" = OLD."rowid";END', + 'auth_sessions_update_time'); + +class OtpCodes extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + OtpCodes(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn rowid = i0.GeneratedColumn( + 'rowid', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT'); + late final i0.GeneratedColumn sessionId = i0.GeneratedColumn( + 'session_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL UNIQUE'); + late final i0.GeneratedColumn resendAttempt = i0.GeneratedColumn( + 'resend_attempt', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const i0.CustomExpression('0')); + late final i0.GeneratedColumn verifyAttempt = i0.GeneratedColumn( + 'verify_attempt', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const i0.CustomExpression('0')); + late final i0.GeneratedColumn updateTime = + i0.GeneratedColumn( + 'update_time', aliasedName, false, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + @override + List get $columns => + [rowid, sessionId, resendAttempt, verifyAttempt, updateTime]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'otp_codes'; + @override + Set get $primaryKey => {rowid}; + @override + i3.OtpCode map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i3.OtpCode( + rowid: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}rowid'])!, + sessionId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}session_id'])!, + resendAttempt: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}resend_attempt'])!, + verifyAttempt: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}verify_attempt'])!, + updateTime: attachedDatabase.typeMapping.read( + const i5.TimestampType(), data['${effectivePrefix}update_time'])!, + ); + } + + @override + OtpCodes createAlias(String alias) { + return OtpCodes(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'CONSTRAINT otp_codes_session_fk FOREIGN KEY(session_id)REFERENCES sessions(session_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class OtpCode extends i0.DataClass implements i0.Insertable { + final int rowid; + + /// The ID of the session. + final String sessionId; + + /// The counter value for the HOTP code. + final int resendAttempt; + + /// The throttling parameter for bruteforce prevention. + final int verifyAttempt; + + /// The time the HOTP code was last used. + final DateTime updateTime; + const OtpCode( + {required this.rowid, + required this.sessionId, + required this.resendAttempt, + required this.verifyAttempt, + required this.updateTime}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['rowid'] = i0.Variable(rowid); + map['session_id'] = i0.Variable(sessionId); + map['resend_attempt'] = i0.Variable(resendAttempt); + map['verify_attempt'] = i0.Variable(verifyAttempt); + map['update_time'] = + i0.Variable(updateTime, const i5.TimestampType()); + return map; + } + + factory OtpCode.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return OtpCode( + rowid: serializer.fromJson(json['rowid']), + sessionId: serializer.fromJson(json['session_id']), + resendAttempt: serializer.fromJson(json['resend_attempt']), + verifyAttempt: serializer.fromJson(json['verify_attempt']), + updateTime: serializer.fromJson(json['update_time']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'rowid': serializer.toJson(rowid), + 'session_id': serializer.toJson(sessionId), + 'resend_attempt': serializer.toJson(resendAttempt), + 'verify_attempt': serializer.toJson(verifyAttempt), + 'update_time': serializer.toJson(updateTime), + }; + } + + i3.OtpCode copyWith( + {int? rowid, + String? sessionId, + int? resendAttempt, + int? verifyAttempt, + DateTime? updateTime}) => + i3.OtpCode( + rowid: rowid ?? this.rowid, + sessionId: sessionId ?? this.sessionId, + resendAttempt: resendAttempt ?? this.resendAttempt, + verifyAttempt: verifyAttempt ?? this.verifyAttempt, + updateTime: updateTime ?? this.updateTime, + ); + OtpCode copyWithCompanion(i3.OtpCodesCompanion data) { + return OtpCode( + rowid: data.rowid.present ? data.rowid.value : this.rowid, + sessionId: data.sessionId.present ? data.sessionId.value : this.sessionId, + resendAttempt: data.resendAttempt.present + ? data.resendAttempt.value + : this.resendAttempt, + verifyAttempt: data.verifyAttempt.present + ? data.verifyAttempt.value + : this.verifyAttempt, + updateTime: + data.updateTime.present ? data.updateTime.value : this.updateTime, + ); + } + + @override + String toString() { + return (StringBuffer('OtpCode(') + ..write('rowid: $rowid, ') + ..write('sessionId: $sessionId, ') + ..write('resendAttempt: $resendAttempt, ') + ..write('verifyAttempt: $verifyAttempt, ') + ..write('updateTime: $updateTime') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(rowid, sessionId, resendAttempt, verifyAttempt, updateTime); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i3.OtpCode && + other.rowid == this.rowid && + other.sessionId == this.sessionId && + other.resendAttempt == this.resendAttempt && + other.verifyAttempt == this.verifyAttempt && + other.updateTime == this.updateTime); +} + +class OtpCodesCompanion extends i0.UpdateCompanion { + final i0.Value rowid; + final i0.Value sessionId; + final i0.Value resendAttempt; + final i0.Value verifyAttempt; + final i0.Value updateTime; + const OtpCodesCompanion({ + this.rowid = const i0.Value.absent(), + this.sessionId = const i0.Value.absent(), + this.resendAttempt = const i0.Value.absent(), + this.verifyAttempt = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + }); + OtpCodesCompanion.insert({ + this.rowid = const i0.Value.absent(), + required String sessionId, + this.resendAttempt = const i0.Value.absent(), + this.verifyAttempt = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + }) : sessionId = i0.Value(sessionId); + static i0.Insertable custom({ + i0.Expression? rowid, + i0.Expression? sessionId, + i0.Expression? resendAttempt, + i0.Expression? verifyAttempt, + i0.Expression? updateTime, + }) { + return i0.RawValuesInsertable({ + if (rowid != null) 'rowid': rowid, + if (sessionId != null) 'session_id': sessionId, + if (resendAttempt != null) 'resend_attempt': resendAttempt, + if (verifyAttempt != null) 'verify_attempt': verifyAttempt, + if (updateTime != null) 'update_time': updateTime, + }); + } + + i3.OtpCodesCompanion copyWith( + {i0.Value? rowid, + i0.Value? sessionId, + i0.Value? resendAttempt, + i0.Value? verifyAttempt, + i0.Value? updateTime}) { + return i3.OtpCodesCompanion( + rowid: rowid ?? this.rowid, + sessionId: sessionId ?? this.sessionId, + resendAttempt: resendAttempt ?? this.resendAttempt, + verifyAttempt: verifyAttempt ?? this.verifyAttempt, + updateTime: updateTime ?? this.updateTime, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + if (sessionId.present) { + map['session_id'] = i0.Variable(sessionId.value); + } + if (resendAttempt.present) { + map['resend_attempt'] = i0.Variable(resendAttempt.value); + } + if (verifyAttempt.present) { + map['verify_attempt'] = i0.Variable(verifyAttempt.value); + } + if (updateTime.present) { + map['update_time'] = + i0.Variable(updateTime.value, const i5.TimestampType()); + } + return map; + } + + @override + String toString() { + return (StringBuffer('OtpCodesCompanion(') + ..write('rowid: $rowid, ') + ..write('sessionId: $sessionId, ') + ..write('resendAttempt: $resendAttempt, ') + ..write('verifyAttempt: $verifyAttempt, ') + ..write('updateTime: $updateTime') + ..write(')')) + .toString(); + } +} + +typedef $OtpCodesCreateCompanionBuilder = i3.OtpCodesCompanion Function({ + i0.Value rowid, + required String sessionId, + i0.Value resendAttempt, + i0.Value verifyAttempt, + i0.Value updateTime, +}); +typedef $OtpCodesUpdateCompanionBuilder = i3.OtpCodesCompanion Function({ + i0.Value rowid, + i0.Value sessionId, + i0.Value resendAttempt, + i0.Value verifyAttempt, + i0.Value updateTime, +}); + +class $OtpCodesFilterComposer + extends i0.FilterComposer { + $OtpCodesFilterComposer(super.$state); + i0.ColumnFilters get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get sessionId => $state.composableBuilder( + column: $state.table.sessionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get resendAttempt => $state.composableBuilder( + column: $state.table.resendAttempt, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get verifyAttempt => $state.composableBuilder( + column: $state.table.verifyAttempt, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $OtpCodesOrderingComposer + extends i0.OrderingComposer { + $OtpCodesOrderingComposer(super.$state); + i0.ColumnOrderings get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get sessionId => $state.composableBuilder( + column: $state.table.sessionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resendAttempt => $state.composableBuilder( + column: $state.table.resendAttempt, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get verifyAttempt => $state.composableBuilder( + column: $state.table.verifyAttempt, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $OtpCodesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i3.OtpCodes, + i3.OtpCode, + i3.$OtpCodesFilterComposer, + i3.$OtpCodesOrderingComposer, + $OtpCodesCreateCompanionBuilder, + $OtpCodesUpdateCompanionBuilder, + ( + i3.OtpCode, + i0.BaseReferences + ), + i3.OtpCode, + i0.PrefetchHooks Function()> { + $OtpCodesTableManager(i0.GeneratedDatabase db, i3.OtpCodes table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i3.$OtpCodesFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i3.$OtpCodesOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + i0.Value sessionId = const i0.Value.absent(), + i0.Value resendAttempt = const i0.Value.absent(), + i0.Value verifyAttempt = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + }) => + i3.OtpCodesCompanion( + rowid: rowid, + sessionId: sessionId, + resendAttempt: resendAttempt, + verifyAttempt: verifyAttempt, + updateTime: updateTime, + ), + createCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + required String sessionId, + i0.Value resendAttempt = const i0.Value.absent(), + i0.Value verifyAttempt = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + }) => + i3.OtpCodesCompanion.insert( + rowid: rowid, + sessionId: sessionId, + resendAttempt: resendAttempt, + verifyAttempt: verifyAttempt, + updateTime: updateTime, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $OtpCodesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i3.OtpCodes, + i3.OtpCode, + i3.$OtpCodesFilterComposer, + i3.$OtpCodesOrderingComposer, + $OtpCodesCreateCompanionBuilder, + $OtpCodesUpdateCompanionBuilder, + ( + i3.OtpCode, + i0.BaseReferences + ), + i3.OtpCode, + i0.PrefetchHooks Function()>; +i0.Index get otpCodesSessionIdx => i0.Index('otp_codes_session_idx', + 'CREATE INDEX IF NOT EXISTS otp_codes_session_idx ON otp_codes (session_id)'); + +class Corks extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Corks(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn corkId = + i0.GeneratedColumn('cork_id', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn cryptoKeyId = + i0.GeneratedColumn('crypto_key_id', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn bearerType = i0.GeneratedColumn( + 'bearer_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn bearerId = i0.GeneratedColumn( + 'bearer_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn audienceType = + i0.GeneratedColumn('audience_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn audienceId = i0.GeneratedColumn( + 'audience_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn issuerType = i0.GeneratedColumn( + 'issuer_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn issuerId = i0.GeneratedColumn( + 'issuer_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn createTime = + i0.GeneratedColumn( + 'create_time', aliasedName, false, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn expireTime = + i0.GeneratedColumn('expire_time', aliasedName, true, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn lastUseTime = + i0.GeneratedColumn('last_use_time', aliasedName, true, + type: const i5.TimestampType(), + requiredDuringInsert: false, + $customConstraints: ''); + @override + List get $columns => [ + corkId, + cryptoKeyId, + bearerType, + bearerId, + audienceType, + audienceId, + issuerType, + issuerId, + createTime, + expireTime, + lastUseTime + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'corks'; + @override + Set get $primaryKey => {corkId}; + @override + i3.Cork map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i3.Cork( + corkId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}cork_id'])!, + cryptoKeyId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}crypto_key_id'])!, + bearerType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}bearer_type']), + bearerId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}bearer_id']), + audienceType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}audience_type']), + audienceId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}audience_id']), + issuerType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}issuer_type']), + issuerId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}issuer_id']), + createTime: attachedDatabase.typeMapping.read( + const i5.TimestampType(), data['${effectivePrefix}create_time'])!, + expireTime: attachedDatabase.typeMapping.read( + const i5.TimestampType(), data['${effectivePrefix}expire_time']), + lastUseTime: attachedDatabase.typeMapping.read( + const i5.TimestampType(), data['${effectivePrefix}last_use_time']), + ); + } + + @override + Corks createAlias(String alias) { + return Corks(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'CONSTRAINT corks_crypto_key_fk FOREIGN KEY(crypto_key_id)REFERENCES crypto_keys(crypto_key_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED', + 'CONSTRAINT corks_bearer_fk FOREIGN KEY(bearer_type, bearer_id)REFERENCES cedar_entities(entity_type, entity_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED', + 'CONSTRAINT corks_audience_fk FOREIGN KEY(audience_type, audience_id)REFERENCES cedar_entities(entity_type, entity_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED', + 'CONSTRAINT corks_issuer_fk FOREIGN KEY(issuer_type, issuer_id)REFERENCES cedar_entities(entity_type, entity_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class Cork extends i0.DataClass implements i0.Insertable { + /// The unique identifier for the cork and its root key. + final i2.Uint8List corkId; + + /// This is typically derived from the organization's root key. + /// + /// The ID of the HMAC key used to sign the cork. + final i2.Uint8List cryptoKeyId; + + /// For example, `Celest::User` or `Celest::ServiceAccount`. + /// + /// The Cedar type of the bearer entity. + final String? bearerType; + + /// The unique identifier of the bearer entity. + final String? bearerId; + + /// For example, `Celest::Service` or `Celest::Api`. + /// + /// The Cedar type of the audience entity. + final String? audienceType; + + /// The unique identifier of the audience entity. + final String? audienceId; + + /// For example, `Celest::Organization` or `Celest::Service`. + /// + /// The Cedar type of the issuer entity. + final String? issuerType; + + /// The unique identifier of the issuer entity. + final String? issuerId; + + /// The time the cork was created. + final DateTime createTime; + + /// The time the cork will expire. + final DateTime? expireTime; + + /// The time the cork was last used. + final DateTime? lastUseTime; + const Cork( + {required this.corkId, + required this.cryptoKeyId, + this.bearerType, + this.bearerId, + this.audienceType, + this.audienceId, + this.issuerType, + this.issuerId, + required this.createTime, + this.expireTime, + this.lastUseTime}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['cork_id'] = i0.Variable(corkId); + map['crypto_key_id'] = i0.Variable(cryptoKeyId); + if (!nullToAbsent || bearerType != null) { + map['bearer_type'] = i0.Variable(bearerType); + } + if (!nullToAbsent || bearerId != null) { + map['bearer_id'] = i0.Variable(bearerId); + } + if (!nullToAbsent || audienceType != null) { + map['audience_type'] = i0.Variable(audienceType); + } + if (!nullToAbsent || audienceId != null) { + map['audience_id'] = i0.Variable(audienceId); + } + if (!nullToAbsent || issuerType != null) { + map['issuer_type'] = i0.Variable(issuerType); + } + if (!nullToAbsent || issuerId != null) { + map['issuer_id'] = i0.Variable(issuerId); + } + map['create_time'] = + i0.Variable(createTime, const i5.TimestampType()); + if (!nullToAbsent || expireTime != null) { + map['expire_time'] = + i0.Variable(expireTime, const i5.TimestampType()); + } + if (!nullToAbsent || lastUseTime != null) { + map['last_use_time'] = + i0.Variable(lastUseTime, const i5.TimestampType()); + } + return map; + } + + factory Cork.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Cork( + corkId: serializer.fromJson(json['cork_id']), + cryptoKeyId: serializer.fromJson(json['crypto_key_id']), + bearerType: serializer.fromJson(json['bearer_type']), + bearerId: serializer.fromJson(json['bearer_id']), + audienceType: serializer.fromJson(json['audience_type']), + audienceId: serializer.fromJson(json['audience_id']), + issuerType: serializer.fromJson(json['issuer_type']), + issuerId: serializer.fromJson(json['issuer_id']), + createTime: serializer.fromJson(json['create_time']), + expireTime: serializer.fromJson(json['expire_time']), + lastUseTime: serializer.fromJson(json['last_use_time']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'cork_id': serializer.toJson(corkId), + 'crypto_key_id': serializer.toJson(cryptoKeyId), + 'bearer_type': serializer.toJson(bearerType), + 'bearer_id': serializer.toJson(bearerId), + 'audience_type': serializer.toJson(audienceType), + 'audience_id': serializer.toJson(audienceId), + 'issuer_type': serializer.toJson(issuerType), + 'issuer_id': serializer.toJson(issuerId), + 'create_time': serializer.toJson(createTime), + 'expire_time': serializer.toJson(expireTime), + 'last_use_time': serializer.toJson(lastUseTime), + }; + } + + i3.Cork copyWith( + {i2.Uint8List? corkId, + i2.Uint8List? cryptoKeyId, + i0.Value bearerType = const i0.Value.absent(), + i0.Value bearerId = const i0.Value.absent(), + i0.Value audienceType = const i0.Value.absent(), + i0.Value audienceId = const i0.Value.absent(), + i0.Value issuerType = const i0.Value.absent(), + i0.Value issuerId = const i0.Value.absent(), + DateTime? createTime, + i0.Value expireTime = const i0.Value.absent(), + i0.Value lastUseTime = const i0.Value.absent()}) => + i3.Cork( + corkId: corkId ?? this.corkId, + cryptoKeyId: cryptoKeyId ?? this.cryptoKeyId, + bearerType: bearerType.present ? bearerType.value : this.bearerType, + bearerId: bearerId.present ? bearerId.value : this.bearerId, + audienceType: + audienceType.present ? audienceType.value : this.audienceType, + audienceId: audienceId.present ? audienceId.value : this.audienceId, + issuerType: issuerType.present ? issuerType.value : this.issuerType, + issuerId: issuerId.present ? issuerId.value : this.issuerId, + createTime: createTime ?? this.createTime, + expireTime: expireTime.present ? expireTime.value : this.expireTime, + lastUseTime: lastUseTime.present ? lastUseTime.value : this.lastUseTime, + ); + Cork copyWithCompanion(i3.CorksCompanion data) { + return Cork( + corkId: data.corkId.present ? data.corkId.value : this.corkId, + cryptoKeyId: + data.cryptoKeyId.present ? data.cryptoKeyId.value : this.cryptoKeyId, + bearerType: + data.bearerType.present ? data.bearerType.value : this.bearerType, + bearerId: data.bearerId.present ? data.bearerId.value : this.bearerId, + audienceType: data.audienceType.present + ? data.audienceType.value + : this.audienceType, + audienceId: + data.audienceId.present ? data.audienceId.value : this.audienceId, + issuerType: + data.issuerType.present ? data.issuerType.value : this.issuerType, + issuerId: data.issuerId.present ? data.issuerId.value : this.issuerId, + createTime: + data.createTime.present ? data.createTime.value : this.createTime, + expireTime: + data.expireTime.present ? data.expireTime.value : this.expireTime, + lastUseTime: + data.lastUseTime.present ? data.lastUseTime.value : this.lastUseTime, + ); + } + + @override + String toString() { + return (StringBuffer('Cork(') + ..write('corkId: $corkId, ') + ..write('cryptoKeyId: $cryptoKeyId, ') + ..write('bearerType: $bearerType, ') + ..write('bearerId: $bearerId, ') + ..write('audienceType: $audienceType, ') + ..write('audienceId: $audienceId, ') + ..write('issuerType: $issuerType, ') + ..write('issuerId: $issuerId, ') + ..write('createTime: $createTime, ') + ..write('expireTime: $expireTime, ') + ..write('lastUseTime: $lastUseTime') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + i0.$driftBlobEquality.hash(corkId), + i0.$driftBlobEquality.hash(cryptoKeyId), + bearerType, + bearerId, + audienceType, + audienceId, + issuerType, + issuerId, + createTime, + expireTime, + lastUseTime); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i3.Cork && + i0.$driftBlobEquality.equals(other.corkId, this.corkId) && + i0.$driftBlobEquality.equals(other.cryptoKeyId, this.cryptoKeyId) && + other.bearerType == this.bearerType && + other.bearerId == this.bearerId && + other.audienceType == this.audienceType && + other.audienceId == this.audienceId && + other.issuerType == this.issuerType && + other.issuerId == this.issuerId && + other.createTime == this.createTime && + other.expireTime == this.expireTime && + other.lastUseTime == this.lastUseTime); +} + +class CorksCompanion extends i0.UpdateCompanion { + final i0.Value corkId; + final i0.Value cryptoKeyId; + final i0.Value bearerType; + final i0.Value bearerId; + final i0.Value audienceType; + final i0.Value audienceId; + final i0.Value issuerType; + final i0.Value issuerId; + final i0.Value createTime; + final i0.Value expireTime; + final i0.Value lastUseTime; + final i0.Value rowid; + const CorksCompanion({ + this.corkId = const i0.Value.absent(), + this.cryptoKeyId = const i0.Value.absent(), + this.bearerType = const i0.Value.absent(), + this.bearerId = const i0.Value.absent(), + this.audienceType = const i0.Value.absent(), + this.audienceId = const i0.Value.absent(), + this.issuerType = const i0.Value.absent(), + this.issuerId = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.expireTime = const i0.Value.absent(), + this.lastUseTime = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CorksCompanion.insert({ + required i2.Uint8List corkId, + required i2.Uint8List cryptoKeyId, + this.bearerType = const i0.Value.absent(), + this.bearerId = const i0.Value.absent(), + this.audienceType = const i0.Value.absent(), + this.audienceId = const i0.Value.absent(), + this.issuerType = const i0.Value.absent(), + this.issuerId = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.expireTime = const i0.Value.absent(), + this.lastUseTime = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : corkId = i0.Value(corkId), + cryptoKeyId = i0.Value(cryptoKeyId); + static i0.Insertable custom({ + i0.Expression? corkId, + i0.Expression? cryptoKeyId, + i0.Expression? bearerType, + i0.Expression? bearerId, + i0.Expression? audienceType, + i0.Expression? audienceId, + i0.Expression? issuerType, + i0.Expression? issuerId, + i0.Expression? createTime, + i0.Expression? expireTime, + i0.Expression? lastUseTime, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (corkId != null) 'cork_id': corkId, + if (cryptoKeyId != null) 'crypto_key_id': cryptoKeyId, + if (bearerType != null) 'bearer_type': bearerType, + if (bearerId != null) 'bearer_id': bearerId, + if (audienceType != null) 'audience_type': audienceType, + if (audienceId != null) 'audience_id': audienceId, + if (issuerType != null) 'issuer_type': issuerType, + if (issuerId != null) 'issuer_id': issuerId, + if (createTime != null) 'create_time': createTime, + if (expireTime != null) 'expire_time': expireTime, + if (lastUseTime != null) 'last_use_time': lastUseTime, + if (rowid != null) 'rowid': rowid, + }); + } + + i3.CorksCompanion copyWith( + {i0.Value? corkId, + i0.Value? cryptoKeyId, + i0.Value? bearerType, + i0.Value? bearerId, + i0.Value? audienceType, + i0.Value? audienceId, + i0.Value? issuerType, + i0.Value? issuerId, + i0.Value? createTime, + i0.Value? expireTime, + i0.Value? lastUseTime, + i0.Value? rowid}) { + return i3.CorksCompanion( + corkId: corkId ?? this.corkId, + cryptoKeyId: cryptoKeyId ?? this.cryptoKeyId, + bearerType: bearerType ?? this.bearerType, + bearerId: bearerId ?? this.bearerId, + audienceType: audienceType ?? this.audienceType, + audienceId: audienceId ?? this.audienceId, + issuerType: issuerType ?? this.issuerType, + issuerId: issuerId ?? this.issuerId, + createTime: createTime ?? this.createTime, + expireTime: expireTime ?? this.expireTime, + lastUseTime: lastUseTime ?? this.lastUseTime, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (corkId.present) { + map['cork_id'] = i0.Variable(corkId.value); + } + if (cryptoKeyId.present) { + map['crypto_key_id'] = i0.Variable(cryptoKeyId.value); + } + if (bearerType.present) { + map['bearer_type'] = i0.Variable(bearerType.value); + } + if (bearerId.present) { + map['bearer_id'] = i0.Variable(bearerId.value); + } + if (audienceType.present) { + map['audience_type'] = i0.Variable(audienceType.value); + } + if (audienceId.present) { + map['audience_id'] = i0.Variable(audienceId.value); + } + if (issuerType.present) { + map['issuer_type'] = i0.Variable(issuerType.value); + } + if (issuerId.present) { + map['issuer_id'] = i0.Variable(issuerId.value); + } + if (createTime.present) { + map['create_time'] = + i0.Variable(createTime.value, const i5.TimestampType()); + } + if (expireTime.present) { + map['expire_time'] = + i0.Variable(expireTime.value, const i5.TimestampType()); + } + if (lastUseTime.present) { + map['last_use_time'] = + i0.Variable(lastUseTime.value, const i5.TimestampType()); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CorksCompanion(') + ..write('corkId: $corkId, ') + ..write('cryptoKeyId: $cryptoKeyId, ') + ..write('bearerType: $bearerType, ') + ..write('bearerId: $bearerId, ') + ..write('audienceType: $audienceType, ') + ..write('audienceId: $audienceId, ') + ..write('issuerType: $issuerType, ') + ..write('issuerId: $issuerId, ') + ..write('createTime: $createTime, ') + ..write('expireTime: $expireTime, ') + ..write('lastUseTime: $lastUseTime, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CorksCreateCompanionBuilder = i3.CorksCompanion Function({ + required i2.Uint8List corkId, + required i2.Uint8List cryptoKeyId, + i0.Value bearerType, + i0.Value bearerId, + i0.Value audienceType, + i0.Value audienceId, + i0.Value issuerType, + i0.Value issuerId, + i0.Value createTime, + i0.Value expireTime, + i0.Value lastUseTime, + i0.Value rowid, +}); +typedef $CorksUpdateCompanionBuilder = i3.CorksCompanion Function({ + i0.Value corkId, + i0.Value cryptoKeyId, + i0.Value bearerType, + i0.Value bearerId, + i0.Value audienceType, + i0.Value audienceId, + i0.Value issuerType, + i0.Value issuerId, + i0.Value createTime, + i0.Value expireTime, + i0.Value lastUseTime, + i0.Value rowid, +}); + +class $CorksFilterComposer + extends i0.FilterComposer { + $CorksFilterComposer(super.$state); + i0.ColumnFilters get corkId => $state.composableBuilder( + column: $state.table.corkId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get bearerType => $state.composableBuilder( + column: $state.table.bearerType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get bearerId => $state.composableBuilder( + column: $state.table.bearerId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get audienceType => $state.composableBuilder( + column: $state.table.audienceType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get audienceId => $state.composableBuilder( + column: $state.table.audienceId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get issuerType => $state.composableBuilder( + column: $state.table.issuerType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get issuerId => $state.composableBuilder( + column: $state.table.issuerId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get lastUseTime => $state.composableBuilder( + column: $state.table.lastUseTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CorksOrderingComposer + extends i0.OrderingComposer { + $CorksOrderingComposer(super.$state); + i0.ColumnOrderings get corkId => $state.composableBuilder( + column: $state.table.corkId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get cryptoKeyId => $state.composableBuilder( + column: $state.table.cryptoKeyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get bearerType => $state.composableBuilder( + column: $state.table.bearerType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get bearerId => $state.composableBuilder( + column: $state.table.bearerId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get audienceType => $state.composableBuilder( + column: $state.table.audienceType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get audienceId => $state.composableBuilder( + column: $state.table.audienceId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get issuerType => $state.composableBuilder( + column: $state.table.issuerType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get issuerId => $state.composableBuilder( + column: $state.table.issuerId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get lastUseTime => $state.composableBuilder( + column: $state.table.lastUseTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CorksTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i3.Corks, + i3.Cork, + i3.$CorksFilterComposer, + i3.$CorksOrderingComposer, + $CorksCreateCompanionBuilder, + $CorksUpdateCompanionBuilder, + (i3.Cork, i0.BaseReferences), + i3.Cork, + i0.PrefetchHooks Function()> { + $CorksTableManager(i0.GeneratedDatabase db, i3.Corks table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i3.$CorksFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i3.$CorksOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value corkId = const i0.Value.absent(), + i0.Value cryptoKeyId = const i0.Value.absent(), + i0.Value bearerType = const i0.Value.absent(), + i0.Value bearerId = const i0.Value.absent(), + i0.Value audienceType = const i0.Value.absent(), + i0.Value audienceId = const i0.Value.absent(), + i0.Value issuerType = const i0.Value.absent(), + i0.Value issuerId = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value expireTime = const i0.Value.absent(), + i0.Value lastUseTime = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i3.CorksCompanion( + corkId: corkId, + cryptoKeyId: cryptoKeyId, + bearerType: bearerType, + bearerId: bearerId, + audienceType: audienceType, + audienceId: audienceId, + issuerType: issuerType, + issuerId: issuerId, + createTime: createTime, + expireTime: expireTime, + lastUseTime: lastUseTime, + rowid: rowid, + ), + createCompanionCallback: ({ + required i2.Uint8List corkId, + required i2.Uint8List cryptoKeyId, + i0.Value bearerType = const i0.Value.absent(), + i0.Value bearerId = const i0.Value.absent(), + i0.Value audienceType = const i0.Value.absent(), + i0.Value audienceId = const i0.Value.absent(), + i0.Value issuerType = const i0.Value.absent(), + i0.Value issuerId = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value expireTime = const i0.Value.absent(), + i0.Value lastUseTime = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i3.CorksCompanion.insert( + corkId: corkId, + cryptoKeyId: cryptoKeyId, + bearerType: bearerType, + bearerId: bearerId, + audienceType: audienceType, + audienceId: audienceId, + issuerType: issuerType, + issuerId: issuerId, + createTime: createTime, + expireTime: expireTime, + lastUseTime: lastUseTime, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CorksProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i3.Corks, + i3.Cork, + i3.$CorksFilterComposer, + i3.$CorksOrderingComposer, + $CorksCreateCompanionBuilder, + $CorksUpdateCompanionBuilder, + (i3.Cork, i0.BaseReferences), + i3.Cork, + i0.PrefetchHooks Function()>; +i0.Index get corksCryptoKeyIdx => i0.Index('corks_crypto_key_idx', + 'CREATE INDEX IF NOT EXISTS corks_crypto_key_idx ON corks (crypto_key_id)'); +i0.Index get corksBearerIdx => i0.Index('corks_bearer_idx', + 'CREATE INDEX IF NOT EXISTS corks_bearer_idx ON corks (bearer_type, bearer_id)'); +i0.Index get corksAudienceIdx => i0.Index('corks_audience_idx', + 'CREATE INDEX IF NOT EXISTS corks_audience_idx ON corks (audience_type, audience_id)'); +i0.Index get corksIssuerIdx => i0.Index('corks_issuer_idx', + 'CREATE INDEX IF NOT EXISTS corks_issuer_idx ON corks (issuer_type, issuer_id)'); + +class AuthDrift extends i7.ModularAccessor { + AuthDrift(i0.GeneratedDatabase db) : super(db); + i8.Future> createCryptoKey( + {required i2.Uint8List cryptoKeyId, + required String keyPurpose, + required String keyAlgorithm, + i2.Uint8List? keyMaterial, + String? externalCryptoKeyId}) { + return customWriteReturning( + 'INSERT INTO crypto_keys (crypto_key_id, key_purpose, key_algorithm, key_material, external_crypto_key_id) VALUES (?1, ?2, ?3, ?4, ?5) ON CONFLICT DO NOTHING RETURNING *', + variables: [ + i0.Variable(cryptoKeyId), + i0.Variable(keyPurpose), + i0.Variable(keyAlgorithm), + i0.Variable(keyMaterial), + i0.Variable(externalCryptoKeyId) + ], + updates: { + cryptoKeys + }).then((rows) => Future.wait(rows.map(cryptoKeys.mapFromRow))); + } + + i0.Selectable getCryptoKey( + {required i2.Uint8List cryptoKeyId}) { + return customSelect('SELECT * FROM crypto_keys WHERE crypto_key_id = ?1', + variables: [ + i0.Variable(cryptoKeyId) + ], + readsFrom: { + cryptoKeys, + }).asyncMap(cryptoKeys.mapFromRow); + } + + i0.Selectable getExternalCryptoKey( + {String? externalCryptoKeyId}) { + return customSelect( + 'SELECT * FROM crypto_keys WHERE external_crypto_key_id = ?1', + variables: [ + i0.Variable(externalCryptoKeyId) + ], + readsFrom: { + cryptoKeys, + }).asyncMap(cryptoKeys.mapFromRow); + } + + i0.Selectable findCryptoKey( + {required String keyPurpose, required String keyAlgorithm}) { + return customSelect( + 'SELECT * FROM crypto_keys WHERE key_purpose = ?1 AND key_algorithm = ?2', + variables: [ + i0.Variable(keyPurpose), + i0.Variable(keyAlgorithm) + ], + readsFrom: { + cryptoKeys, + }).asyncMap(cryptoKeys.mapFromRow); + } + + Future deleteCryptoKey({required i2.Uint8List cryptoKeyId}) { + return customUpdate( + 'DELETE FROM crypto_keys WHERE crypto_key_id = ?1', + variables: [i0.Variable(cryptoKeyId)], + updates: {cryptoKeys}, + updateKind: i0.UpdateKind.delete, + ); + } + + i8.Future> createSession( + {required String sessionId, + required i2.Uint8List cryptoKeyId, + String? userId, + required i4.SessionClient clientInfo, + required i4.AuthenticationFactor authenticationFactor, + i4.SessionState? state, + String? ipAddress, + required DateTime expireTime, + DateTime? cancelTime, + String? externalSessionId}) { + return customWriteReturning( + 'INSERT INTO sessions (session_id, crypto_key_id, user_id, client_info, authentication_factor, state, ip_address, expire_time, cancel_time, external_session_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) RETURNING *', + variables: [ + i0.Variable(sessionId), + i0.Variable(cryptoKeyId), + i0.Variable(userId), + i0.Variable( + i3.Sessions.$converterclientInfo.toSql(clientInfo)), + i0.Variable(i3.Sessions.$converterauthenticationFactor + .toSql(authenticationFactor)), + i0.Variable(i3.Sessions.$converterstaten.toSql(state)), + i0.Variable(ipAddress), + i0.Variable(expireTime, const i5.TimestampType()), + i0.Variable(cancelTime, const i5.TimestampType()), + i0.Variable(externalSessionId) + ], + updates: { + sessions + }).then((rows) => Future.wait(rows.map(sessions.mapFromRow))); + } + + i0.Selectable getSession({String? sessionId}) { + return customSelect( + 'SELECT * FROM sessions WHERE(sessions.session_id = ?1 OR sessions.external_session_id = ?1)AND sessions.expire_time > unixepoch(\'now\', \'subsec\') AND sessions.cancel_time IS NULL', + variables: [ + i0.Variable(sessionId) + ], + readsFrom: { + sessions, + }).asyncMap(sessions.mapFromRow); + } + + i8.Future> updateSession( + {i4.SessionState? state, String? sessionId}) { + return customWriteReturning( + 'UPDATE sessions SET state = ?1 WHERE session_id = ?2 OR external_session_id = ?2 RETURNING *', + variables: [ + i0.Variable( + i3.Sessions.$converterstaten.toSql(state)), + i0.Variable(sessionId) + ], + updates: {sessions}, + updateKind: i0.UpdateKind.update) + .then((rows) => Future.wait(rows.map(sessions.mapFromRow))); + } + + Future deleteSession({String? sessionId}) { + return customUpdate( + 'DELETE FROM sessions WHERE session_id = ?1 OR external_session_id = ?1', + variables: [i0.Variable(sessionId)], + updates: {sessions}, + updateKind: i0.UpdateKind.delete, + ); + } + + Future cancelSession({String? sessionId}) { + return customUpdate( + 'UPDATE sessions SET cancel_time = unixepoch(\'now\', \'subsec\') WHERE session_id = ?1 OR external_session_id = ?1', + variables: [i0.Variable(sessionId)], + updates: {sessions}, + updateKind: i0.UpdateKind.update, + ); + } + + Future createCork( + {required i2.Uint8List corkId, + required i2.Uint8List cryptoKeyId, + String? bearerType, + String? bearerId, + String? audienceType, + String? audienceId, + String? issuerType, + String? issuerId, + DateTime? expireTime}) { + return customInsert( + '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)', + variables: [ + i0.Variable(corkId), + i0.Variable(cryptoKeyId), + i0.Variable(bearerType), + i0.Variable(bearerId), + i0.Variable(audienceType), + i0.Variable(audienceId), + i0.Variable(issuerType), + i0.Variable(issuerId), + i0.Variable(expireTime, const i5.TimestampType()) + ], + updates: {corks}, + ); + } + + i0.Selectable getCork({required i2.Uint8List corkId}) { + return customSelect('SELECT * FROM corks WHERE cork_id = ?1', variables: [ + i0.Variable(corkId) + ], readsFrom: { + corks, + }).asyncMap(corks.mapFromRow); + } + + Future deleteCork({required i2.Uint8List corkId}) { + return customUpdate( + 'DELETE FROM corks WHERE cork_id = ?1', + variables: [i0.Variable(corkId)], + updates: {corks}, + updateKind: i0.UpdateKind.delete, + ); + } + + i8.Future> deleteCorksForEntity( + {String? bearerType, + String? bearerId, + String? audienceType, + String? audienceId, + String? issuerType, + String? issuerId}) { + return customWriteReturning( + 'DELETE FROM corks WHERE(?1 IS NULL OR bearer_type = ?1)AND(?2 IS NULL OR bearer_id = ?2)AND(?3 IS NULL OR audience_type = ?3)AND(?4 IS NULL OR audience_id = ?4)AND(?5 IS NULL OR issuer_type = ?5)AND(?6 IS NULL OR issuer_id = ?6)RETURNING cork_id', + variables: [ + i0.Variable(bearerType), + i0.Variable(bearerId), + i0.Variable(audienceType), + i0.Variable(audienceId), + i0.Variable(issuerType), + i0.Variable(issuerId) + ], + updates: {corks}, + updateKind: i0.UpdateKind.delete) + .then((rows) => rows + .map((i0.QueryRow row) => row.read('cork_id')) + .toList()); + } + + i8.Future> upsertOtpCode( + {required String sessionId, int? resendAttempt, int? verifyAttempt}) { + return customWriteReturning( + 'INSERT INTO otp_codes (session_id, resend_attempt, verify_attempt) VALUES (?1, coalesce(?2, 0), coalesce(?3, 0)) ON CONFLICT (session_id) DO UPDATE SET resend_attempt = coalesce(?2, resend_attempt), verify_attempt = coalesce(?3, verify_attempt) RETURNING *', + variables: [ + i0.Variable(sessionId), + i0.Variable(resendAttempt), + i0.Variable(verifyAttempt) + ], + updates: { + otpCodes + }).then((rows) => Future.wait(rows.map(otpCodes.mapFromRow))); + } + + i8.Future> updateOtpCode( + {int? resendAttempt, int? verifyAttempt, required String sessionId}) { + return customWriteReturning( + 'UPDATE otp_codes SET resend_attempt = coalesce(?1, resend_attempt), verify_attempt = coalesce(?2, verify_attempt) WHERE session_id = ?3 RETURNING *', + variables: [ + i0.Variable(resendAttempt), + i0.Variable(verifyAttempt), + i0.Variable(sessionId) + ], + updates: {otpCodes}, + updateKind: i0.UpdateKind.update) + .then((rows) => Future.wait(rows.map(otpCodes.mapFromRow))); + } + + i0.Selectable getOtpCode({required String sessionId}) { + return customSelect('SELECT * FROM otp_codes WHERE session_id = ?1', + variables: [ + i0.Variable(sessionId) + ], + readsFrom: { + otpCodes, + }).asyncMap(otpCodes.mapFromRow); + } + + i3.CryptoKeys get cryptoKeys => i7.ReadDatabaseContainer(attachedDatabase) + .resultSet('crypto_keys'); + i3.Sessions get sessions => i7.ReadDatabaseContainer(attachedDatabase) + .resultSet('sessions'); + i3.Corks get corks => + i7.ReadDatabaseContainer(attachedDatabase).resultSet('corks'); + i3.OtpCodes get otpCodes => i7.ReadDatabaseContainer(attachedDatabase) + .resultSet('otp_codes'); +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/cedar.drift b/services/celest_cloud_auth/lib/src/database/schema/cedar.drift new file mode 100644 index 00000000..17a46d9e --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/cedar.drift @@ -0,0 +1,630 @@ +import 'schema_imports.dart'; + +-- The Cedar database schema is a simple graph database that stores entities and relationships between them. +-- +-- The schema consists of three tables: +-- 1. cedar_types: Stores the fully qualified name (FQN) of the Cedar type. +-- 2. cedar_entities: Stores the unique identifier of entities. This table is updated as entities are created throughout the system. +-- 3. cedar_relationships: Stores the relationships between entities. This table is updated as relationships change throughout the system. +-- +-- The schema is designed to be simple and flexible, allowing for the creation of new Cedar types and relationships as needed. +-- To avoid duplication and improve speed, we store precisely the data needed for Cedar to make authorization decisions in the format it needs. + +-- Stores the fully qualified name (FQN) of known Cedar types. +-- +-- Only types in this table may be used in ReBAC policies. +CREATE TABLE IF NOT EXISTS cedar_types ( + -- The fully qualified Cedar type, e.g. Celest::Organization + fqn TEXT NOT NULL PRIMARY KEY +); + +-- Stores the unique identifier of entities. +-- +-- Entity IDs are duplicated into this table to avoid JOINs when querying relationships and allow +-- for efficient querying of relationships. +CREATE TABLE IF NOT EXISTS cedar_entities ( + -- A reference to the Cedar type of the entity. + entity_type TEXT NOT NULL REFERENCES cedar_types(fqn), + + -- The unique identifier of the entity. + entity_id TEXT NOT NULL, + + -- The attributes of the entity. + -- + -- These should only be static attributes that are needed for authorization decisions. + attribute_json TEXT NOT NULL DEFAULT '{}' + MAPPED BY `const CedarAttributesConverter()`, + + -- A JSON representation of the entity. + entity_json TEXT NOT NULL GENERATED ALWAYS AS (json_object('type', entity_type, 'id', entity_id)) VIRTUAL + MAPPED BY `const CedarEntityUidConverter()`, + + CONSTRAINT cedar_entities_pk PRIMARY KEY (entity_type, entity_id) ON CONFLICT IGNORE +) WITHOUT ROWID; + +-- Stores the relationships between entities. +-- +-- Relationships are stored as a directed graph, where the child entity is related to the parent entity. +-- For Cedar ReBAC policies, the parent information is all that's needed to make authorization decisions. +CREATE TABLE IF NOT EXISTS cedar_relationships ( + -- The entity (child) of the relationship. + entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, + entity_json TEXT NOT NULL GENERATED ALWAYS AS (json_object('type', entity_type, 'id', entity_id)) VIRTUAL + MAPPED BY `const CedarEntityUidConverter()`, + + -- The parent (ancestor) of the relationship. + parent_type TEXT NOT NULL, parent_id TEXT NOT NULL, + parent_json TEXT NOT NULL GENERATED ALWAYS AS (json_object('type', parent_type, 'id', parent_id)) VIRTUAL + MAPPED BY `const CedarEntityUidConverter()`, + + CONSTRAINT cedar_relationships_pk + PRIMARY KEY (entity_type, entity_id, parent_type, parent_id) ON CONFLICT IGNORE, + + CONSTRAINT cedar_relationships_fk_entity + FOREIGN KEY (entity_type, entity_id) REFERENCES cedar_entities(entity_type, entity_id), + + CONSTRAINT cedar_relationships_fk_parent + FOREIGN KEY (parent_type, parent_id) REFERENCES cedar_entities(entity_type, entity_id) +) WITHOUT ROWID; + +-- Indexes for the cedar_relationships foreign keys. +CREATE INDEX IF NOT EXISTS cedar_relationships_fk_entity_idx ON cedar_relationships(entity_type, entity_id); +CREATE INDEX IF NOT EXISTS cedar_relationships_fk_parent_idx ON cedar_relationships(parent_type, parent_id); + +-- Stores static Cedar policies used globally in evaluations. +CREATE TABLE IF NOT EXISTS cedar_policies ( + -- Immutable. The unique identifier for the policy. + -- + -- Maps to the `uid` field in the Protobuf. + -- + -- Format: pol_ + id TEXT NOT NULL PRIMARY KEY, + + -- The primary alias for the policy, mapping to the `@id` annotation. + -- + -- While Cedar does not require policies to have IDs, we enforce their usage to improve + -- auditability and debuggability. + policy_id TEXT NOT NULL UNIQUE, + + -- The policy in JSON format. + -- + -- Type: JSON[package:cedar/cedar.dart#Policy] + policy TEXT NOT NULL + MAPPED BY `const CedarPolicyConverter()`, + + -- The policy's enforcement level. + -- + -- Dry-run policies are captured in the audit log but do not affect presentation of the + -- authorization decision. + -- + -- Type: integer (enforced=1, dry-run=0) + enforcement_level INTEGER NOT NULL DEFAULT 1, + + CHECK (enforcement_level IN (0, 1)) +); + +-- Stores Cedar policy templates which may be attached to entities for evaluation. +CREATE TABLE IF NOT EXISTS cedar_policy_templates ( + -- Immutable. The unique identifier for the template. + -- + -- Maps to the `uid` field in the Protobuf. + -- + -- Format: polt_ + id TEXT NOT NULL PRIMARY KEY, + + -- The primary alias for the template, mapping to the `@id` annotation. + -- + -- While Cedar does not require templates to have IDs, we enforce their usage to improve + -- auditability and debuggability. + template_id TEXT NOT NULL UNIQUE, + + -- The template in JSON format. + -- + -- Type: JSON[package:cedar/cedar.dart#Policy] + template TEXT NOT NULL + MAPPED BY `const CedarPolicyConverter()`, + + CHECK (template IS NOT NULL OR template IS NOT NULL) +); + +-- Stores linked Cedar policy templates. +CREATE TABLE IF NOT EXISTS cedar_policy_template_links ( + -- Immutable. The unique identifier for the policy. + -- + -- Maps to the `uid` field in the Protobuf. + -- + -- Format: polk_ + id TEXT NOT NULL PRIMARY KEY, + + -- The primary alias of the policy created by this link. + policy_id TEXT NOT NULL UNIQUE, + + -- The policy template ID. + template_id TEXT NOT NULL, + + -- The linked principal slot of the template. + principal_type TEXT, principal_id TEXT, + + -- The linked resource slot of the template. + resource_type TEXT, resource_id TEXT, + + -- The policy's enforcement level. + -- + -- Dry-run policies are captured in the audit log but do not affect presentation of the + -- authorization decision. + -- + -- Type: integer (enforced=1, dry-run=0) + enforcement_level INTEGER NOT NULL DEFAULT 1, + + CHECK ( + principal_type IS NOT NULL AND principal_id IS NOT NULL + OR resource_type IS NOT NULL AND resource_id IS NOT NULL + ), + CHECK (enforcement_level IN (0, 1)), + + CONSTRAINT cedar_policy_template_links_fk_template_id + FOREIGN KEY (template_id) REFERENCES cedar_policy_templates(template_id) ON UPDATE CASCADE ON DELETE CASCADE, + + CONSTRAINT cedar_policy_template_links_fk_principal + FOREIGN KEY (principal_type, principal_id) REFERENCES cedar_entities(entity_type, entity_id) ON DELETE CASCADE, + + CONSTRAINT cedar_policy_template_links_fk_resource + FOREIGN KEY (resource_type, resource_id) REFERENCES cedar_entities(entity_type, entity_id) ON DELETE CASCADE +); + +-- Indexes for the `cedar.policy_template_links` foreign keys. +CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_template_id_idx ON cedar_policy_template_links(template_id); +CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_principal_idx ON cedar_policy_template_links(principal_type, principal_id); +CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_resource_idx ON cedar_policy_template_links(resource_type, resource_id); + +-- The audit log for Cedar authorization requests. +-- +-- This is an append-only table that stores the history of all authorization requests. +-- Currently, the hub is responsible for writing to this table. In the future, Cedar will +-- be able to write to this table directly. +-- +-- Foreign keys are not used in this table to prevent conflicts and ensure the table remains +-- append-only. +CREATE TABLE IF NOT EXISTS cedar_authorization_logs( + -- The unique identifier of the audit log entry. + rowid INTEGER PRIMARY KEY AUTOINCREMENT, + + -- The time the audit log entry was created. + -- + -- Type: unixepoch.subsec + create_time DATETIME NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + -- The log's expiration time if a TTL has been set. + -- + -- Type: unixepoch | null + expire_time DATETIME, + + -- The requesting principal's entity type. + -- + -- Type: string + principal_type TEXT, + + -- The requesting principal's entity ID. + -- + -- Type: string + principal_id TEXT, + + -- The requested action's entity type. + -- + -- Type: string + action_type TEXT, + + -- The requested action's entity ID. + -- + -- Type: string + action_id TEXT, + + -- The requested resource's entity type. + -- + -- Type: string + resource_type TEXT, + + -- The requested resource's entity ID. + -- + -- Type: string + resource_id TEXT, + + -- The context of the request + -- + -- Type: JSON + -- JSON: `{ [key: string]: string }` + context_json TEXT NOT NULL DEFAULT '{}' + MAPPED BY `const CedarAttributesConverter()`, + + -- The authorization decision. + -- + -- Type: bool (true=allow, false=deny) + decision BOOLEAN NOT NULL, + + -- The reasons for the authorization decision. + -- + -- Type: JSON + -- JSON: []string + reasons_json TEXT NOT NULL DEFAULT '[]' + MAPPED BY `const CedarAuthorizationReasonsConverter()`, + + -- The errors encountered during the authorization decision. + -- + -- Type: JSON + -- JSON: + -- ```json + -- [ + -- { + -- "policy": "policy_id", + -- "message": "string" + -- } + -- ] + -- ``` + errors_json TEXT NOT NULL DEFAULT '[]' + MAPPED BY `const CedarAuthorizationErrorsConverter()` +); + +-- name: GetEntityClosure :one +-- Retrives the transitive closure of an entity and its parents. +getEntityClosure: WITH RECURSIVE + parents AS ( + -- Start by selecting the entity and all its direct parents. + SELECT + e.entity_type, + e.entity_id, + e.parent_type, + e.parent_id, + e.parent_json + FROM + cedar_relationships e + WHERE + e.entity_type = :type + AND e.entity_id = :id + + UNION ALL + + -- Recursively select the parents of the parents. + SELECT + p.entity_type, + p.entity_id, + p.parent_type, + p.parent_id, + p.parent_json + FROM + cedar_relationships p + INNER JOIN + parents a + ON p.entity_id = a.parent_id + AND p.entity_type = a.parent_type + ), + -- Select all entities that are in the closure. This is necessary to ensure that all + -- entities, even those without parents, are included in the final result. + entities AS ( + SELECT * FROM cedar_entities + WHERE + (entity_type, entity_id) IN (SELECT entity_type, entity_id FROM parents) + OR (entity_type, entity_id) IN (SELECT parent_type, parent_id FROM parents) + OR (entity_type, entity_id) = (:type, :id) + ) +-- Cedar expects the Entities to be a slice of entity objects, each taking the form: +-- +-- ```json +-- { +-- "uid": { +-- "type": "string", +-- "id": "string" +-- }, +-- "attrs": object, +-- "parents": [ +-- { +-- "type": "string", +-- "id": "string" +-- } +-- ] +-- } +-- ``` +SELECT + json_group_array( + json_object( + 'uid', entity_json, + 'attrs', json(attribute_json), + 'parents', ( + SELECT json_group_array( + -- json() is used to remove the outer quotes from the json string. + -- Not sure why this is necessary here but not with entity_json. + json(parent_json) + ) + FROM parents p + WHERE + p.entity_type = e.entity_type + AND p.entity_id = e.entity_id + ) + ) + ) MAPPED BY `const CedarEntityClosureConverter()` +FROM entities e; + +-- name: DebugDumpCedar :many +-- FOR DEBUGGING ONLY. Returns all entities and relationships in the cedar database. +debugDumpCedar: SELECT + e.entity_type, + e.entity_id, + coalesce(r.parent_type, ''), + coalesce(r.parent_id, '') +FROM + cedar_entities e +LEFT JOIN + cedar_relationships r + ON e.entity_type = r.entity_type + AND e.entity_id = r.entity_id; + +-- name: CreatePolicy :exec +-- Creates a policy in the cedar database. +createPolicy: INSERT INTO cedar_policies(id, policy_id, policy, enforcement_level) +VALUES (:id, :policy_id, :policy, :enforcement_level) +ON CONFLICT(policy_id) DO UPDATE SET + policy = excluded.policy, + enforcement_level = excluded.enforcement_level; + +upsertPolicy: INSERT INTO cedar_policies( + id, + policy_id, + policy, + enforcement_level +) +VALUES ( + :id, + :policy_id, + :policy, + :enforcement_level +) +ON CONFLICT(policy_id) DO UPDATE SET + policy = excluded.policy, + enforcement_level = excluded.enforcement_level; + +-- name: CreatePolicyTemplate :exec +-- Creates a policy template in the cedar database. +createPolicyTemplate: INSERT INTO cedar_policy_templates(id, template_id, template) +VALUES (:id, :template_id, :template) +ON CONFLICT(template_id) DO UPDATE SET + template = excluded.template; + +upsertPolicyTemplate: INSERT INTO cedar_policy_templates( + id, + template_id, + template +) +VALUES ( + :id, + :template_id, + :template +) +ON CONFLICT(template_id) DO UPDATE SET + template = excluded.template; + +-- Upserts a policy template link in the cedar database. +upsertPolicyTemplateLink: INSERT INTO cedar_policy_template_links( + id, + policy_id, + template_id, + principal_type, + principal_id, + resource_type, + resource_id, + enforcement_level +) +VALUES ( + :id, + :policy_id, + :template_id, + :principal_type, + :principal_id, + :resource_type, + :resource_id, + :enforcement_level +) +ON CONFLICT(policy_id) DO UPDATE SET + principal_type = excluded.principal_type, + principal_id = excluded.principal_id, + resource_type = excluded.resource_type, + resource_id = excluded.resource_id, + enforcement_level = excluded.enforcement_level; + +-- name: LinkPolicyTemplate :exec +-- Creates a policy by linking a template. +linkPolicyTemplate: INSERT INTO cedar_policy_template_links( + id, + policy_id, + template_id, + principal_type, + principal_id, + resource_type, + resource_id, + enforcement_level +) +VALUES ( + :id, + :policy_id, + :template_id, + :principal_type, + :principal_id, + :resource_type, + :resource_id, + :enforcement_level +); + +-- name: ListPolicies :many +-- Lists all policies and linked templates in the cedar database. +listEffectivePolicies: WITH template_links AS ( + SELECT + l.id, + l.policy_id, + t.template, + l.principal_type, + l.principal_id, + l.resource_type, + l.resource_id, + l.enforcement_level + FROM cedar_policy_templates t + INNER JOIN cedar_policy_template_links l + ON l.template_id = t.id +) +SELECT + id, + template_id AS policy_id, + template AS policy, + NULL AS principal_type, + NULL AS principal_id, + NULL AS resource_type, + NULL AS resource_id, + NULL AS enforcement_level +FROM + cedar_policy_templates +UNION ALL +SELECT + id, + policy_id, + template AS policy, + principal_type, + principal_id, + resource_type, + resource_id, + enforcement_level +FROM + template_links +UNION ALL +SELECT + id, + policy_id, + policy, + NULL AS principal_type, + NULL AS principal_id, + NULL AS resource_type, + NULL AS resource_id, + enforcement_level +FROM + cedar_policies; + +-- name: UpdatePolicy :exec +-- Updates a policy in the cedar database. +updatePolicy(:policy AS TEXT OR NULL, :enforcement_level AS INT OR NULL): UPDATE cedar_policies +SET policy = coalesce(:policy, policy), + enforcement_level = coalesce(:enforcement_level, enforcement_level) +WHERE id = :policy_id OR policy_id = :policy_id; + +-- name: UpdatePolicyTemplate :exec +-- Updates a policy template in the cedar database. +updatePolicyTemplate(:template AS TEXT OR NULL): UPDATE cedar_policy_templates +SET template = coalesce(:template, template) +WHERE id = :template_id OR template_id = :template_id; + +-- name: UpdatePolicyTemplateLink :exec +-- Updates a policy template link in the cedar database. +updatePolicyTemplateLink(:enforcement_level AS INT OR NULL): UPDATE cedar_policy_template_links +SET enforcement_level = coalesce(:enforcement_level, enforcement_level) +WHERE id = :policy_id OR policy_id = :policy_id; + +-- name: DeletePolicy :one +-- Deletes a policy from the cedar database. +deletePolicy: DELETE FROM cedar_policies +WHERE id = :policy_id OR policy_id = :policy_id +RETURNING *; + +-- name: DeletePolicyTemplate :one +-- Deletes a policy template from the cedar database. +deletePolicyTemplate: DELETE FROM cedar_policy_templates +WHERE id = :template_id OR template_id = :template_id +RETURNING *; + +-- name: DeletePolicyTemplateLink :one +-- Deletes a policy template link from the cedar database. +deletePolicyTemplateLink: DELETE FROM cedar_policy_template_links +WHERE id = :policy_id OR policy_id = :policy_id +RETURNING *; + +-- name: RecordAuthorization :exec +-- Records an authorization decision in the cedar database. +recordAuthorization: INSERT INTO cedar_authorization_logs( + principal_type, + principal_id, + action_type, + action_id, + resource_type, + resource_id, + context_json, + decision, + reasons_json, + errors_json +) +VALUES ( + :principal_type, + :principal_id, + :action_type, + :action_id, + :resource_type, + :resource_id, + :context_json, + :decision, + :reasons_json, + :errors_json +); + +-- name: CreateCedarRelationship :exec +-- Creates a relationship in the cedar database. +createRelationship: INSERT INTO cedar_relationships( + entity_type, + entity_id, + parent_type, + parent_id +) +VALUES ( + :entity_type, + :entity_id, + :parent_type, + :parent_id +) +ON CONFLICT(entity_type, entity_id, parent_type, parent_id) DO NOTHING; + +-- name: CreateCedarEntity :one +-- Creates an entity in the cedar database. +createEntity: INSERT INTO cedar_entities( + entity_type, + entity_id, + attribute_json +) +VALUES ( + :entity_type, + :entity_id, + :attribute_json +) +ON CONFLICT(entity_type, entity_id) DO UPDATE +SET attribute_json = excluded.attribute_json +RETURNING *; + +createType: + INSERT INTO cedar_types(fqn) + VALUES (:fqn) + ON CONFLICT(fqn) DO NOTHING; + +-- name: GetCedarEntity :one +-- Retrieves an entity from the cedar database. +getEntity: SELECT * +FROM + cedar_entities +WHERE + entity_type = :entity_type +AND entity_id = :entity_id; + +-- name: GetCedarRelationships :one +-- Retrieves all relationships for an entity from the cedar database. +getRelationship: SELECT * +FROM + cedar_relationships +WHERE + entity_type = :entity_type +AND entity_id = :entity_id; + +-- name: UpdateCedarEntity :exec +-- Updates an entity in the cedar database. +updateEntity(:attribute_json AS TEXT OR NULL): UPDATE cedar_entities +SET attribute_json = coalesce(:attribute_json, attribute_json) +WHERE entity_type = :entity_type AND entity_id = :entity_id; diff --git a/services/celest_cloud_auth/lib/src/database/schema/cedar.drift.dart b/services/celest_cloud_auth/lib/src/database/schema/cedar.drift.dart new file mode 100644 index 00000000..2b092644 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/cedar.drift.dart @@ -0,0 +1,3777 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_cloud_auth/src/database/schema/cedar.drift.dart' as i1; +import 'package:cedar/src/model/value.dart' as i2; +import 'package:celest_cloud_auth/src/database/schema/converters/cedar_converters.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'package:cedar/src/model/policy.dart' as i5; +import 'package:cedar/src/authorization/authorization_response.dart' as i6; +import 'dart:async' as i7; + +class CedarTypes extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarTypes(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn fqn = i0.GeneratedColumn( + 'fqn', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + @override + List get $columns => [fqn]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_types'; + @override + Set get $primaryKey => {fqn}; + @override + i1.CedarType map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarType( + fqn: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}fqn'])!, + ); + } + + @override + CedarTypes createAlias(String alias) { + return CedarTypes(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class CedarType extends i0.DataClass implements i0.Insertable { + /// The fully qualified Cedar type, e.g. Celest::Organization + final String fqn; + const CedarType({required this.fqn}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['fqn'] = i0.Variable(fqn); + return map; + } + + factory CedarType.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarType( + fqn: serializer.fromJson(json['fqn']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'fqn': serializer.toJson(fqn), + }; + } + + i1.CedarType copyWith({String? fqn}) => i1.CedarType( + fqn: fqn ?? this.fqn, + ); + CedarType copyWithCompanion(i1.CedarTypesCompanion data) { + return CedarType( + fqn: data.fqn.present ? data.fqn.value : this.fqn, + ); + } + + @override + String toString() { + return (StringBuffer('CedarType(') + ..write('fqn: $fqn') + ..write(')')) + .toString(); + } + + @override + int get hashCode => fqn.hashCode; + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarType && other.fqn == this.fqn); +} + +class CedarTypesCompanion extends i0.UpdateCompanion { + final i0.Value fqn; + final i0.Value rowid; + const CedarTypesCompanion({ + this.fqn = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CedarTypesCompanion.insert({ + required String fqn, + this.rowid = const i0.Value.absent(), + }) : fqn = i0.Value(fqn); + static i0.Insertable custom({ + i0.Expression? fqn, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (fqn != null) 'fqn': fqn, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CedarTypesCompanion copyWith( + {i0.Value? fqn, i0.Value? rowid}) { + return i1.CedarTypesCompanion( + fqn: fqn ?? this.fqn, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (fqn.present) { + map['fqn'] = i0.Variable(fqn.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarTypesCompanion(') + ..write('fqn: $fqn, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CedarTypesCreateCompanionBuilder = i1.CedarTypesCompanion Function({ + required String fqn, + i0.Value rowid, +}); +typedef $CedarTypesUpdateCompanionBuilder = i1.CedarTypesCompanion Function({ + i0.Value fqn, + i0.Value rowid, +}); + +class $CedarTypesFilterComposer + extends i0.FilterComposer { + $CedarTypesFilterComposer(super.$state); + i0.ColumnFilters get fqn => $state.composableBuilder( + column: $state.table.fqn, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CedarTypesOrderingComposer + extends i0.OrderingComposer { + $CedarTypesOrderingComposer(super.$state); + i0.ColumnOrderings get fqn => $state.composableBuilder( + column: $state.table.fqn, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarTypesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarTypes, + i1.CedarType, + i1.$CedarTypesFilterComposer, + i1.$CedarTypesOrderingComposer, + $CedarTypesCreateCompanionBuilder, + $CedarTypesUpdateCompanionBuilder, + ( + i1.CedarType, + i0.BaseReferences + ), + i1.CedarType, + i0.PrefetchHooks Function()> { + $CedarTypesTableManager(i0.GeneratedDatabase db, i1.CedarTypes table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CedarTypesFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CedarTypesOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value fqn = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarTypesCompanion( + fqn: fqn, + rowid: rowid, + ), + createCompanionCallback: ({ + required String fqn, + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarTypesCompanion.insert( + fqn: fqn, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarTypesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarTypes, + i1.CedarType, + i1.$CedarTypesFilterComposer, + i1.$CedarTypesOrderingComposer, + $CedarTypesCreateCompanionBuilder, + $CedarTypesUpdateCompanionBuilder, + ( + i1.CedarType, + i0.BaseReferences + ), + i1.CedarType, + i0.PrefetchHooks Function()>; + +class CedarEntities extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarEntities(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn entityType = i0.GeneratedColumn( + 'entity_type', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES cedar_types(fqn)'); + late final i0.GeneratedColumn entityId = i0.GeneratedColumn( + 'entity_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumnWithTypeConverter, String> + attributeJson = i0.GeneratedColumn( + 'attribute_json', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'{}\'', + defaultValue: const i0.CustomExpression('\'{}\'')) + .withConverter>( + i1.CedarEntities.$converterattributeJson); + late final i0.GeneratedColumnWithTypeConverter entityJson = i0.GeneratedColumn( + 'entity_json', aliasedName, false, + generatedAs: i0.GeneratedAs( + const i0.CustomExpression( + 'json_object(\'type\', entity_type, \'id\', entity_id)'), + false), + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL GENERATED ALWAYS AS (json_object(\'type\', entity_type, \'id\', entity_id)) VIRTUAL') + .withConverter(i1.CedarEntities.$converterentityJson); + @override + List get $columns => + [entityType, entityId, attributeJson, entityJson]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_entities'; + @override + Set get $primaryKey => {entityType, entityId}; + @override + i1.CedarEntity map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarEntity( + entityType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}entity_type'])!, + entityId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}entity_id'])!, + attributeJson: i1.CedarEntities.$converterattributeJson.fromSql( + attachedDatabase.typeMapping.read(i0.DriftSqlType.string, + data['${effectivePrefix}attribute_json'])!), + entityJson: i1.CedarEntities.$converterentityJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}entity_json'])!), + ); + } + + @override + CedarEntities createAlias(String alias) { + return CedarEntities(attachedDatabase, alias); + } + + static i0.TypeConverter, String> + $converterattributeJson = const i3.CedarAttributesConverter(); + static i0.TypeConverter $converterentityJson = + const i3.CedarEntityUidConverter(); + @override + bool get withoutRowId => true; + @override + List get customConstraints => const [ + 'CONSTRAINT cedar_entities_pk PRIMARY KEY(entity_type, entity_id)ON CONFLICT IGNORE' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CedarEntity extends i0.DataClass + implements i0.Insertable { + /// A reference to the Cedar type of the entity. + final String entityType; + + /// The unique identifier of the entity. + final String entityId; + + /// These should only be static attributes that are needed for authorization decisions. + /// + /// The attributes of the entity. + final Map attributeJson; + + /// A JSON representation of the entity. + final i2.EntityUid entityJson; + const CedarEntity( + {required this.entityType, + required this.entityId, + required this.attributeJson, + required this.entityJson}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['entity_type'] = i0.Variable(entityType); + map['entity_id'] = i0.Variable(entityId); + { + map['attribute_json'] = i0.Variable( + i1.CedarEntities.$converterattributeJson.toSql(attributeJson)); + } + return map; + } + + factory CedarEntity.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarEntity( + entityType: serializer.fromJson(json['entity_type']), + entityId: serializer.fromJson(json['entity_id']), + attributeJson: + serializer.fromJson>(json['attribute_json']), + entityJson: serializer.fromJson(json['entity_json']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'entity_type': serializer.toJson(entityType), + 'entity_id': serializer.toJson(entityId), + 'attribute_json': serializer.toJson>(attributeJson), + 'entity_json': serializer.toJson(entityJson), + }; + } + + i1.CedarEntity copyWith( + {String? entityType, + String? entityId, + Map? attributeJson, + i2.EntityUid? entityJson}) => + i1.CedarEntity( + entityType: entityType ?? this.entityType, + entityId: entityId ?? this.entityId, + attributeJson: attributeJson ?? this.attributeJson, + entityJson: entityJson ?? this.entityJson, + ); + @override + String toString() { + return (StringBuffer('CedarEntity(') + ..write('entityType: $entityType, ') + ..write('entityId: $entityId, ') + ..write('attributeJson: $attributeJson, ') + ..write('entityJson: $entityJson') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(entityType, entityId, attributeJson, entityJson); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarEntity && + other.entityType == this.entityType && + other.entityId == this.entityId && + other.attributeJson == this.attributeJson && + other.entityJson == this.entityJson); +} + +class CedarEntitiesCompanion extends i0.UpdateCompanion { + final i0.Value entityType; + final i0.Value entityId; + final i0.Value> attributeJson; + const CedarEntitiesCompanion({ + this.entityType = const i0.Value.absent(), + this.entityId = const i0.Value.absent(), + this.attributeJson = const i0.Value.absent(), + }); + CedarEntitiesCompanion.insert({ + required String entityType, + required String entityId, + this.attributeJson = const i0.Value.absent(), + }) : entityType = i0.Value(entityType), + entityId = i0.Value(entityId); + static i0.Insertable custom({ + i0.Expression? entityType, + i0.Expression? entityId, + i0.Expression? attributeJson, + }) { + return i0.RawValuesInsertable({ + if (entityType != null) 'entity_type': entityType, + if (entityId != null) 'entity_id': entityId, + if (attributeJson != null) 'attribute_json': attributeJson, + }); + } + + i1.CedarEntitiesCompanion copyWith( + {i0.Value? entityType, + i0.Value? entityId, + i0.Value>? attributeJson}) { + return i1.CedarEntitiesCompanion( + entityType: entityType ?? this.entityType, + entityId: entityId ?? this.entityId, + attributeJson: attributeJson ?? this.attributeJson, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (entityType.present) { + map['entity_type'] = i0.Variable(entityType.value); + } + if (entityId.present) { + map['entity_id'] = i0.Variable(entityId.value); + } + if (attributeJson.present) { + map['attribute_json'] = i0.Variable( + i1.CedarEntities.$converterattributeJson.toSql(attributeJson.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarEntitiesCompanion(') + ..write('entityType: $entityType, ') + ..write('entityId: $entityId, ') + ..write('attributeJson: $attributeJson') + ..write(')')) + .toString(); + } +} + +typedef $CedarEntitiesCreateCompanionBuilder = i1.CedarEntitiesCompanion + Function({ + required String entityType, + required String entityId, + i0.Value> attributeJson, +}); +typedef $CedarEntitiesUpdateCompanionBuilder = i1.CedarEntitiesCompanion + Function({ + i0.Value entityType, + i0.Value entityId, + i0.Value> attributeJson, +}); + +class $CedarEntitiesFilterComposer + extends i0.FilterComposer { + $CedarEntitiesFilterComposer(super.$state); + i0.ColumnFilters get entityId => $state.composableBuilder( + column: $state.table.entityId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters, + Map, String> + get attributeJson => $state.composableBuilder( + column: $state.table.attributeJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get entityJson => $state.composableBuilder( + column: $state.table.entityJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i1.$CedarTypesFilterComposer get entityType { + final i1.$CedarTypesFilterComposer composer = $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.entityType, + referencedTable: i4.ReadDatabaseContainer($state.db) + .resultSet('cedar_types'), + getReferencedColumn: (t) => t.fqn, + builder: (joinBuilder, parentComposers) => i1.$CedarTypesFilterComposer( + i0.ComposerState( + $state.db, + i4.ReadDatabaseContainer($state.db) + .resultSet('cedar_types'), + joinBuilder, + parentComposers))); + return composer; + } +} + +class $CedarEntitiesOrderingComposer + extends i0.OrderingComposer { + $CedarEntitiesOrderingComposer(super.$state); + i0.ColumnOrderings get entityId => $state.composableBuilder( + column: $state.table.entityId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get attributeJson => $state.composableBuilder( + column: $state.table.attributeJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get entityJson => $state.composableBuilder( + column: $state.table.entityJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i1.$CedarTypesOrderingComposer get entityType { + final i1.$CedarTypesOrderingComposer composer = $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.entityType, + referencedTable: i4.ReadDatabaseContainer($state.db) + .resultSet('cedar_types'), + getReferencedColumn: (t) => t.fqn, + builder: (joinBuilder, parentComposers) => + i1.$CedarTypesOrderingComposer(i0.ComposerState( + $state.db, + i4.ReadDatabaseContainer($state.db) + .resultSet('cedar_types'), + joinBuilder, + parentComposers))); + return composer; + } +} + +class $CedarEntitiesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarEntities, + i1.CedarEntity, + i1.$CedarEntitiesFilterComposer, + i1.$CedarEntitiesOrderingComposer, + $CedarEntitiesCreateCompanionBuilder, + $CedarEntitiesUpdateCompanionBuilder, + ( + i1.CedarEntity, + i0.BaseReferences + ), + i1.CedarEntity, + i0.PrefetchHooks Function({bool entityType})> { + $CedarEntitiesTableManager(i0.GeneratedDatabase db, i1.CedarEntities table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CedarEntitiesFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CedarEntitiesOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value entityType = const i0.Value.absent(), + i0.Value entityId = const i0.Value.absent(), + i0.Value> attributeJson = + const i0.Value.absent(), + }) => + i1.CedarEntitiesCompanion( + entityType: entityType, + entityId: entityId, + attributeJson: attributeJson, + ), + createCompanionCallback: ({ + required String entityType, + required String entityId, + i0.Value> attributeJson = + const i0.Value.absent(), + }) => + i1.CedarEntitiesCompanion.insert( + entityType: entityType, + entityId: entityId, + attributeJson: attributeJson, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarEntitiesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarEntities, + i1.CedarEntity, + i1.$CedarEntitiesFilterComposer, + i1.$CedarEntitiesOrderingComposer, + $CedarEntitiesCreateCompanionBuilder, + $CedarEntitiesUpdateCompanionBuilder, + ( + i1.CedarEntity, + i0.BaseReferences + ), + i1.CedarEntity, + i0.PrefetchHooks Function({bool entityType})>; + +class CedarRelationships extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarRelationships(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn entityType = i0.GeneratedColumn( + 'entity_type', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn entityId = i0.GeneratedColumn( + 'entity_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumnWithTypeConverter entityJson = i0.GeneratedColumn( + 'entity_json', aliasedName, false, + generatedAs: i0.GeneratedAs( + const i0.CustomExpression( + 'json_object(\'type\', entity_type, \'id\', entity_id)'), + false), + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL GENERATED ALWAYS AS (json_object(\'type\', entity_type, \'id\', entity_id)) VIRTUAL') + .withConverter(i1.CedarRelationships.$converterentityJson); + late final i0.GeneratedColumn parentType = i0.GeneratedColumn( + 'parent_type', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn parentId = i0.GeneratedColumn( + 'parent_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumnWithTypeConverter parentJson = i0.GeneratedColumn( + 'parent_json', aliasedName, false, + generatedAs: i0.GeneratedAs( + const i0.CustomExpression( + 'json_object(\'type\', parent_type, \'id\', parent_id)'), + false), + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL GENERATED ALWAYS AS (json_object(\'type\', parent_type, \'id\', parent_id)) VIRTUAL') + .withConverter(i1.CedarRelationships.$converterparentJson); + @override + List get $columns => + [entityType, entityId, entityJson, parentType, parentId, parentJson]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_relationships'; + @override + Set get $primaryKey => + {entityType, entityId, parentType, parentId}; + @override + i1.CedarRelationship map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarRelationship( + entityType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}entity_type'])!, + entityId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}entity_id'])!, + entityJson: i1.CedarRelationships.$converterentityJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}entity_json'])!), + parentType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}parent_type'])!, + parentId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}parent_id'])!, + parentJson: i1.CedarRelationships.$converterparentJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}parent_json'])!), + ); + } + + @override + CedarRelationships createAlias(String alias) { + return CedarRelationships(attachedDatabase, alias); + } + + static i0.TypeConverter $converterentityJson = + const i3.CedarEntityUidConverter(); + static i0.TypeConverter $converterparentJson = + const i3.CedarEntityUidConverter(); + @override + bool get withoutRowId => true; + @override + List get customConstraints => const [ + 'CONSTRAINT cedar_relationships_pk PRIMARY KEY(entity_type, entity_id, parent_type, parent_id)ON CONFLICT IGNORE', + 'CONSTRAINT cedar_relationships_fk_entity FOREIGN KEY(entity_type, entity_id)REFERENCES cedar_entities(entity_type, entity_id)', + 'CONSTRAINT cedar_relationships_fk_parent FOREIGN KEY(parent_type, parent_id)REFERENCES cedar_entities(entity_type, entity_id)' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CedarRelationship extends i0.DataClass + implements i0.Insertable { + /// The entity (child) of the relationship. + final String entityType; + final String entityId; + final i2.EntityUid entityJson; + + /// The parent (ancestor) of the relationship. + final String parentType; + final String parentId; + final i2.EntityUid parentJson; + const CedarRelationship( + {required this.entityType, + required this.entityId, + required this.entityJson, + required this.parentType, + required this.parentId, + required this.parentJson}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['entity_type'] = i0.Variable(entityType); + map['entity_id'] = i0.Variable(entityId); + map['parent_type'] = i0.Variable(parentType); + map['parent_id'] = i0.Variable(parentId); + return map; + } + + factory CedarRelationship.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarRelationship( + entityType: serializer.fromJson(json['entity_type']), + entityId: serializer.fromJson(json['entity_id']), + entityJson: serializer.fromJson(json['entity_json']), + parentType: serializer.fromJson(json['parent_type']), + parentId: serializer.fromJson(json['parent_id']), + parentJson: serializer.fromJson(json['parent_json']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'entity_type': serializer.toJson(entityType), + 'entity_id': serializer.toJson(entityId), + 'entity_json': serializer.toJson(entityJson), + 'parent_type': serializer.toJson(parentType), + 'parent_id': serializer.toJson(parentId), + 'parent_json': serializer.toJson(parentJson), + }; + } + + i1.CedarRelationship copyWith( + {String? entityType, + String? entityId, + i2.EntityUid? entityJson, + String? parentType, + String? parentId, + i2.EntityUid? parentJson}) => + i1.CedarRelationship( + entityType: entityType ?? this.entityType, + entityId: entityId ?? this.entityId, + entityJson: entityJson ?? this.entityJson, + parentType: parentType ?? this.parentType, + parentId: parentId ?? this.parentId, + parentJson: parentJson ?? this.parentJson, + ); + @override + String toString() { + return (StringBuffer('CedarRelationship(') + ..write('entityType: $entityType, ') + ..write('entityId: $entityId, ') + ..write('entityJson: $entityJson, ') + ..write('parentType: $parentType, ') + ..write('parentId: $parentId, ') + ..write('parentJson: $parentJson') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + entityType, entityId, entityJson, parentType, parentId, parentJson); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarRelationship && + other.entityType == this.entityType && + other.entityId == this.entityId && + other.entityJson == this.entityJson && + other.parentType == this.parentType && + other.parentId == this.parentId && + other.parentJson == this.parentJson); +} + +class CedarRelationshipsCompanion + extends i0.UpdateCompanion { + final i0.Value entityType; + final i0.Value entityId; + final i0.Value parentType; + final i0.Value parentId; + const CedarRelationshipsCompanion({ + this.entityType = const i0.Value.absent(), + this.entityId = const i0.Value.absent(), + this.parentType = const i0.Value.absent(), + this.parentId = const i0.Value.absent(), + }); + CedarRelationshipsCompanion.insert({ + required String entityType, + required String entityId, + required String parentType, + required String parentId, + }) : entityType = i0.Value(entityType), + entityId = i0.Value(entityId), + parentType = i0.Value(parentType), + parentId = i0.Value(parentId); + static i0.Insertable custom({ + i0.Expression? entityType, + i0.Expression? entityId, + i0.Expression? parentType, + i0.Expression? parentId, + }) { + return i0.RawValuesInsertable({ + if (entityType != null) 'entity_type': entityType, + if (entityId != null) 'entity_id': entityId, + if (parentType != null) 'parent_type': parentType, + if (parentId != null) 'parent_id': parentId, + }); + } + + i1.CedarRelationshipsCompanion copyWith( + {i0.Value? entityType, + i0.Value? entityId, + i0.Value? parentType, + i0.Value? parentId}) { + return i1.CedarRelationshipsCompanion( + entityType: entityType ?? this.entityType, + entityId: entityId ?? this.entityId, + parentType: parentType ?? this.parentType, + parentId: parentId ?? this.parentId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (entityType.present) { + map['entity_type'] = i0.Variable(entityType.value); + } + if (entityId.present) { + map['entity_id'] = i0.Variable(entityId.value); + } + if (parentType.present) { + map['parent_type'] = i0.Variable(parentType.value); + } + if (parentId.present) { + map['parent_id'] = i0.Variable(parentId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarRelationshipsCompanion(') + ..write('entityType: $entityType, ') + ..write('entityId: $entityId, ') + ..write('parentType: $parentType, ') + ..write('parentId: $parentId') + ..write(')')) + .toString(); + } +} + +typedef $CedarRelationshipsCreateCompanionBuilder + = i1.CedarRelationshipsCompanion Function({ + required String entityType, + required String entityId, + required String parentType, + required String parentId, +}); +typedef $CedarRelationshipsUpdateCompanionBuilder + = i1.CedarRelationshipsCompanion Function({ + i0.Value entityType, + i0.Value entityId, + i0.Value parentType, + i0.Value parentId, +}); + +class $CedarRelationshipsFilterComposer + extends i0.FilterComposer { + $CedarRelationshipsFilterComposer(super.$state); + i0.ColumnFilters get entityType => $state.composableBuilder( + column: $state.table.entityType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get entityId => $state.composableBuilder( + column: $state.table.entityId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get entityJson => $state.composableBuilder( + column: $state.table.entityJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get parentType => $state.composableBuilder( + column: $state.table.parentType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get parentId => $state.composableBuilder( + column: $state.table.parentId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get parentJson => $state.composableBuilder( + column: $state.table.parentJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); +} + +class $CedarRelationshipsOrderingComposer + extends i0.OrderingComposer { + $CedarRelationshipsOrderingComposer(super.$state); + i0.ColumnOrderings get entityType => $state.composableBuilder( + column: $state.table.entityType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get entityId => $state.composableBuilder( + column: $state.table.entityId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get entityJson => $state.composableBuilder( + column: $state.table.entityJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get parentType => $state.composableBuilder( + column: $state.table.parentType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get parentId => $state.composableBuilder( + column: $state.table.parentId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get parentJson => $state.composableBuilder( + column: $state.table.parentJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarRelationshipsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarRelationships, + i1.CedarRelationship, + i1.$CedarRelationshipsFilterComposer, + i1.$CedarRelationshipsOrderingComposer, + $CedarRelationshipsCreateCompanionBuilder, + $CedarRelationshipsUpdateCompanionBuilder, + ( + i1.CedarRelationship, + i0.BaseReferences + ), + i1.CedarRelationship, + i0.PrefetchHooks Function()> { + $CedarRelationshipsTableManager( + i0.GeneratedDatabase db, i1.CedarRelationships table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CedarRelationshipsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: i1 + .$CedarRelationshipsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value entityType = const i0.Value.absent(), + i0.Value entityId = const i0.Value.absent(), + i0.Value parentType = const i0.Value.absent(), + i0.Value parentId = const i0.Value.absent(), + }) => + i1.CedarRelationshipsCompanion( + entityType: entityType, + entityId: entityId, + parentType: parentType, + parentId: parentId, + ), + createCompanionCallback: ({ + required String entityType, + required String entityId, + required String parentType, + required String parentId, + }) => + i1.CedarRelationshipsCompanion.insert( + entityType: entityType, + entityId: entityId, + parentType: parentType, + parentId: parentId, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarRelationshipsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarRelationships, + i1.CedarRelationship, + i1.$CedarRelationshipsFilterComposer, + i1.$CedarRelationshipsOrderingComposer, + $CedarRelationshipsCreateCompanionBuilder, + $CedarRelationshipsUpdateCompanionBuilder, + ( + i1.CedarRelationship, + i0.BaseReferences + ), + i1.CedarRelationship, + i0.PrefetchHooks Function()>; +i0.Index get cedarRelationshipsFkEntityIdx => i0.Index( + 'cedar_relationships_fk_entity_idx', + 'CREATE INDEX IF NOT EXISTS cedar_relationships_fk_entity_idx ON cedar_relationships (entity_type, entity_id)'); +i0.Index get cedarRelationshipsFkParentIdx => i0.Index( + 'cedar_relationships_fk_parent_idx', + 'CREATE INDEX IF NOT EXISTS cedar_relationships_fk_parent_idx ON cedar_relationships (parent_type, parent_id)'); + +class CedarPolicies extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarPolicies(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn policyId = i0.GeneratedColumn( + 'policy_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL UNIQUE'); + late final i0.GeneratedColumnWithTypeConverter policy = + i0.GeneratedColumn('policy', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(i1.CedarPolicies.$converterpolicy); + late final i0.GeneratedColumn enforcementLevel = i0.GeneratedColumn( + 'enforcement_level', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1', + defaultValue: const i0.CustomExpression('1')); + @override + List get $columns => + [id, policyId, policy, enforcementLevel]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_policies'; + @override + Set get $primaryKey => {id}; + @override + i1.CedarPolicy map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarPolicy( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!, + policyId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}policy_id'])!, + policy: i1.CedarPolicies.$converterpolicy.fromSql(attachedDatabase + .typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}policy'])!), + enforcementLevel: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, data['${effectivePrefix}enforcement_level'])!, + ); + } + + @override + CedarPolicies createAlias(String alias) { + return CedarPolicies(attachedDatabase, alias); + } + + static i0.TypeConverter $converterpolicy = + const i3.CedarPolicyConverter(); + @override + List get customConstraints => + const ['CHECK(enforcement_level IN (0, 1))']; + @override + bool get dontWriteConstraints => true; +} + +class CedarPolicy extends i0.DataClass + implements i0.Insertable { + /// Format: pol_ + /// + /// Maps to the `uid` field in the Protobuf. + /// + /// Immutable. The unique identifier for the policy. + final String id; + + /// auditability and debuggability. + /// While Cedar does not require policies to have IDs, we enforce their usage to improve + /// + /// The primary alias for the policy, mapping to the `@id` annotation. + final String policyId; + + /// Type: JSON[package:cedar/cedar.dart#Policy] + /// + /// The policy in JSON format. + final i5.Policy policy; + + /// Type: integer (enforced=1, dry-run=0) + /// + /// authorization decision. + /// Dry-run policies are captured in the audit log but do not affect presentation of the + /// + /// The policy's enforcement level. + final int enforcementLevel; + const CedarPolicy( + {required this.id, + required this.policyId, + required this.policy, + required this.enforcementLevel}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['policy_id'] = i0.Variable(policyId); + { + map['policy'] = + i0.Variable(i1.CedarPolicies.$converterpolicy.toSql(policy)); + } + map['enforcement_level'] = i0.Variable(enforcementLevel); + return map; + } + + factory CedarPolicy.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarPolicy( + id: serializer.fromJson(json['id']), + policyId: serializer.fromJson(json['policy_id']), + policy: serializer.fromJson(json['policy']), + enforcementLevel: serializer.fromJson(json['enforcement_level']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'policy_id': serializer.toJson(policyId), + 'policy': serializer.toJson(policy), + 'enforcement_level': serializer.toJson(enforcementLevel), + }; + } + + i1.CedarPolicy copyWith( + {String? id, + String? policyId, + i5.Policy? policy, + int? enforcementLevel}) => + i1.CedarPolicy( + id: id ?? this.id, + policyId: policyId ?? this.policyId, + policy: policy ?? this.policy, + enforcementLevel: enforcementLevel ?? this.enforcementLevel, + ); + CedarPolicy copyWithCompanion(i1.CedarPoliciesCompanion data) { + return CedarPolicy( + id: data.id.present ? data.id.value : this.id, + policyId: data.policyId.present ? data.policyId.value : this.policyId, + policy: data.policy.present ? data.policy.value : this.policy, + enforcementLevel: data.enforcementLevel.present + ? data.enforcementLevel.value + : this.enforcementLevel, + ); + } + + @override + String toString() { + return (StringBuffer('CedarPolicy(') + ..write('id: $id, ') + ..write('policyId: $policyId, ') + ..write('policy: $policy, ') + ..write('enforcementLevel: $enforcementLevel') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, policyId, policy, enforcementLevel); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarPolicy && + other.id == this.id && + other.policyId == this.policyId && + other.policy == this.policy && + other.enforcementLevel == this.enforcementLevel); +} + +class CedarPoliciesCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value policyId; + final i0.Value policy; + final i0.Value enforcementLevel; + final i0.Value rowid; + const CedarPoliciesCompanion({ + this.id = const i0.Value.absent(), + this.policyId = const i0.Value.absent(), + this.policy = const i0.Value.absent(), + this.enforcementLevel = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CedarPoliciesCompanion.insert({ + required String id, + required String policyId, + required i5.Policy policy, + this.enforcementLevel = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : id = i0.Value(id), + policyId = i0.Value(policyId), + policy = i0.Value(policy); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? policyId, + i0.Expression? policy, + i0.Expression? enforcementLevel, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (policyId != null) 'policy_id': policyId, + if (policy != null) 'policy': policy, + if (enforcementLevel != null) 'enforcement_level': enforcementLevel, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CedarPoliciesCompanion copyWith( + {i0.Value? id, + i0.Value? policyId, + i0.Value? policy, + i0.Value? enforcementLevel, + i0.Value? rowid}) { + return i1.CedarPoliciesCompanion( + id: id ?? this.id, + policyId: policyId ?? this.policyId, + policy: policy ?? this.policy, + enforcementLevel: enforcementLevel ?? this.enforcementLevel, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (policyId.present) { + map['policy_id'] = i0.Variable(policyId.value); + } + if (policy.present) { + map['policy'] = i0.Variable( + i1.CedarPolicies.$converterpolicy.toSql(policy.value)); + } + if (enforcementLevel.present) { + map['enforcement_level'] = i0.Variable(enforcementLevel.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarPoliciesCompanion(') + ..write('id: $id, ') + ..write('policyId: $policyId, ') + ..write('policy: $policy, ') + ..write('enforcementLevel: $enforcementLevel, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CedarPoliciesCreateCompanionBuilder = i1.CedarPoliciesCompanion + Function({ + required String id, + required String policyId, + required i5.Policy policy, + i0.Value enforcementLevel, + i0.Value rowid, +}); +typedef $CedarPoliciesUpdateCompanionBuilder = i1.CedarPoliciesCompanion + Function({ + i0.Value id, + i0.Value policyId, + i0.Value policy, + i0.Value enforcementLevel, + i0.Value rowid, +}); + +class $CedarPoliciesFilterComposer + extends i0.FilterComposer { + $CedarPoliciesFilterComposer(super.$state); + i0.ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get policyId => $state.composableBuilder( + column: $state.table.policyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters get policy => + $state.composableBuilder( + column: $state.table.policy, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get enforcementLevel => $state.composableBuilder( + column: $state.table.enforcementLevel, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CedarPoliciesOrderingComposer + extends i0.OrderingComposer { + $CedarPoliciesOrderingComposer(super.$state); + i0.ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get policyId => $state.composableBuilder( + column: $state.table.policyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get policy => $state.composableBuilder( + column: $state.table.policy, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get enforcementLevel => $state.composableBuilder( + column: $state.table.enforcementLevel, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarPoliciesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarPolicies, + i1.CedarPolicy, + i1.$CedarPoliciesFilterComposer, + i1.$CedarPoliciesOrderingComposer, + $CedarPoliciesCreateCompanionBuilder, + $CedarPoliciesUpdateCompanionBuilder, + ( + i1.CedarPolicy, + i0.BaseReferences + ), + i1.CedarPolicy, + i0.PrefetchHooks Function()> { + $CedarPoliciesTableManager(i0.GeneratedDatabase db, i1.CedarPolicies table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CedarPoliciesFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CedarPoliciesOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value policyId = const i0.Value.absent(), + i0.Value policy = const i0.Value.absent(), + i0.Value enforcementLevel = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPoliciesCompanion( + id: id, + policyId: policyId, + policy: policy, + enforcementLevel: enforcementLevel, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String policyId, + required i5.Policy policy, + i0.Value enforcementLevel = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPoliciesCompanion.insert( + id: id, + policyId: policyId, + policy: policy, + enforcementLevel: enforcementLevel, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarPoliciesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarPolicies, + i1.CedarPolicy, + i1.$CedarPoliciesFilterComposer, + i1.$CedarPoliciesOrderingComposer, + $CedarPoliciesCreateCompanionBuilder, + $CedarPoliciesUpdateCompanionBuilder, + ( + i1.CedarPolicy, + i0.BaseReferences + ), + i1.CedarPolicy, + i0.PrefetchHooks Function()>; + +class CedarPolicyTemplates extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarPolicyTemplates(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn templateId = i0.GeneratedColumn( + 'template_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL UNIQUE'); + late final i0.GeneratedColumnWithTypeConverter template = + i0.GeneratedColumn('template', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(i1.CedarPolicyTemplates.$convertertemplate); + @override + List get $columns => [id, templateId, template]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_policy_templates'; + @override + Set get $primaryKey => {id}; + @override + i1.CedarPolicyTemplate map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarPolicyTemplate( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!, + templateId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}template_id'])!, + template: i1.CedarPolicyTemplates.$convertertemplate.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}template'])!), + ); + } + + @override + CedarPolicyTemplates createAlias(String alias) { + return CedarPolicyTemplates(attachedDatabase, alias); + } + + static i0.TypeConverter $convertertemplate = + const i3.CedarPolicyConverter(); + @override + List get customConstraints => + const ['CHECK(template IS NOT NULL OR template IS NOT NULL)']; + @override + bool get dontWriteConstraints => true; +} + +class CedarPolicyTemplate extends i0.DataClass + implements i0.Insertable { + /// Format: polt_ + /// + /// Maps to the `uid` field in the Protobuf. + /// + /// Immutable. The unique identifier for the template. + final String id; + + /// auditability and debuggability. + /// While Cedar does not require templates to have IDs, we enforce their usage to improve + /// + /// The primary alias for the template, mapping to the `@id` annotation. + final String templateId; + + /// Type: JSON[package:cedar/cedar.dart#Policy] + /// + /// The template in JSON format. + final i5.Policy template; + const CedarPolicyTemplate( + {required this.id, required this.templateId, required this.template}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['template_id'] = i0.Variable(templateId); + { + map['template'] = i0.Variable( + i1.CedarPolicyTemplates.$convertertemplate.toSql(template)); + } + return map; + } + + factory CedarPolicyTemplate.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarPolicyTemplate( + id: serializer.fromJson(json['id']), + templateId: serializer.fromJson(json['template_id']), + template: serializer.fromJson(json['template']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'template_id': serializer.toJson(templateId), + 'template': serializer.toJson(template), + }; + } + + i1.CedarPolicyTemplate copyWith( + {String? id, String? templateId, i5.Policy? template}) => + i1.CedarPolicyTemplate( + id: id ?? this.id, + templateId: templateId ?? this.templateId, + template: template ?? this.template, + ); + CedarPolicyTemplate copyWithCompanion(i1.CedarPolicyTemplatesCompanion data) { + return CedarPolicyTemplate( + id: data.id.present ? data.id.value : this.id, + templateId: + data.templateId.present ? data.templateId.value : this.templateId, + template: data.template.present ? data.template.value : this.template, + ); + } + + @override + String toString() { + return (StringBuffer('CedarPolicyTemplate(') + ..write('id: $id, ') + ..write('templateId: $templateId, ') + ..write('template: $template') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, templateId, template); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarPolicyTemplate && + other.id == this.id && + other.templateId == this.templateId && + other.template == this.template); +} + +class CedarPolicyTemplatesCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value templateId; + final i0.Value template; + final i0.Value rowid; + const CedarPolicyTemplatesCompanion({ + this.id = const i0.Value.absent(), + this.templateId = const i0.Value.absent(), + this.template = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CedarPolicyTemplatesCompanion.insert({ + required String id, + required String templateId, + required i5.Policy template, + this.rowid = const i0.Value.absent(), + }) : id = i0.Value(id), + templateId = i0.Value(templateId), + template = i0.Value(template); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? templateId, + i0.Expression? template, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (templateId != null) 'template_id': templateId, + if (template != null) 'template': template, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CedarPolicyTemplatesCompanion copyWith( + {i0.Value? id, + i0.Value? templateId, + i0.Value? template, + i0.Value? rowid}) { + return i1.CedarPolicyTemplatesCompanion( + id: id ?? this.id, + templateId: templateId ?? this.templateId, + template: template ?? this.template, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (templateId.present) { + map['template_id'] = i0.Variable(templateId.value); + } + if (template.present) { + map['template'] = i0.Variable( + i1.CedarPolicyTemplates.$convertertemplate.toSql(template.value)); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarPolicyTemplatesCompanion(') + ..write('id: $id, ') + ..write('templateId: $templateId, ') + ..write('template: $template, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CedarPolicyTemplatesCreateCompanionBuilder + = i1.CedarPolicyTemplatesCompanion Function({ + required String id, + required String templateId, + required i5.Policy template, + i0.Value rowid, +}); +typedef $CedarPolicyTemplatesUpdateCompanionBuilder + = i1.CedarPolicyTemplatesCompanion Function({ + i0.Value id, + i0.Value templateId, + i0.Value template, + i0.Value rowid, +}); + +class $CedarPolicyTemplatesFilterComposer + extends i0.FilterComposer { + $CedarPolicyTemplatesFilterComposer(super.$state); + i0.ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get templateId => $state.composableBuilder( + column: $state.table.templateId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get template => $state.composableBuilder( + column: $state.table.template, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); +} + +class $CedarPolicyTemplatesOrderingComposer + extends i0.OrderingComposer { + $CedarPolicyTemplatesOrderingComposer(super.$state); + i0.ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get templateId => $state.composableBuilder( + column: $state.table.templateId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get template => $state.composableBuilder( + column: $state.table.template, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarPolicyTemplatesTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarPolicyTemplates, + i1.CedarPolicyTemplate, + i1.$CedarPolicyTemplatesFilterComposer, + i1.$CedarPolicyTemplatesOrderingComposer, + $CedarPolicyTemplatesCreateCompanionBuilder, + $CedarPolicyTemplatesUpdateCompanionBuilder, + ( + i1.CedarPolicyTemplate, + i0.BaseReferences + ), + i1.CedarPolicyTemplate, + i0.PrefetchHooks Function()> { + $CedarPolicyTemplatesTableManager( + i0.GeneratedDatabase db, i1.CedarPolicyTemplates table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: i1 + .$CedarPolicyTemplatesFilterComposer(i0.ComposerState(db, table)), + orderingComposer: i1.$CedarPolicyTemplatesOrderingComposer( + i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value templateId = const i0.Value.absent(), + i0.Value template = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPolicyTemplatesCompanion( + id: id, + templateId: templateId, + template: template, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String templateId, + required i5.Policy template, + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPolicyTemplatesCompanion.insert( + id: id, + templateId: templateId, + template: template, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarPolicyTemplatesProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarPolicyTemplates, + i1.CedarPolicyTemplate, + i1.$CedarPolicyTemplatesFilterComposer, + i1.$CedarPolicyTemplatesOrderingComposer, + $CedarPolicyTemplatesCreateCompanionBuilder, + $CedarPolicyTemplatesUpdateCompanionBuilder, + ( + i1.CedarPolicyTemplate, + i0.BaseReferences + ), + i1.CedarPolicyTemplate, + i0.PrefetchHooks Function()>; + +class CedarPolicyTemplateLinks extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarPolicyTemplateLinks(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn policyId = i0.GeneratedColumn( + 'policy_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL UNIQUE'); + late final i0.GeneratedColumn templateId = i0.GeneratedColumn( + 'template_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn principalType = + i0.GeneratedColumn('principal_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn principalId = + i0.GeneratedColumn('principal_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn resourceType = + i0.GeneratedColumn('resource_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn resourceId = i0.GeneratedColumn( + 'resource_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn enforcementLevel = i0.GeneratedColumn( + 'enforcement_level', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1', + defaultValue: const i0.CustomExpression('1')); + @override + List get $columns => [ + id, + policyId, + templateId, + principalType, + principalId, + resourceType, + resourceId, + enforcementLevel + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_policy_template_links'; + @override + Set get $primaryKey => {id}; + @override + i1.CedarPolicyTemplateLink map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarPolicyTemplateLink( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!, + policyId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}policy_id'])!, + templateId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}template_id'])!, + principalType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}principal_type']), + principalId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}principal_id']), + resourceType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}resource_type']), + resourceId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}resource_id']), + enforcementLevel: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, data['${effectivePrefix}enforcement_level'])!, + ); + } + + @override + CedarPolicyTemplateLinks createAlias(String alias) { + return CedarPolicyTemplateLinks(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'CHECK(principal_type IS NOT NULL AND principal_id IS NOT NULL OR resource_type IS NOT NULL AND resource_id IS NOT NULL)', + 'CHECK(enforcement_level IN (0, 1))', + 'CONSTRAINT cedar_policy_template_links_fk_template_id FOREIGN KEY(template_id)REFERENCES cedar_policy_templates(template_id)ON UPDATE CASCADE ON DELETE CASCADE', + 'CONSTRAINT cedar_policy_template_links_fk_principal FOREIGN KEY(principal_type, principal_id)REFERENCES cedar_entities(entity_type, entity_id)ON DELETE CASCADE', + 'CONSTRAINT cedar_policy_template_links_fk_resource FOREIGN KEY(resource_type, resource_id)REFERENCES cedar_entities(entity_type, entity_id)ON DELETE CASCADE' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CedarPolicyTemplateLink extends i0.DataClass + implements i0.Insertable { + /// Format: polk_ + /// + /// Maps to the `uid` field in the Protobuf. + /// + /// Immutable. The unique identifier for the policy. + final String id; + + /// The primary alias of the policy created by this link. + final String policyId; + + /// The policy template ID. + final String templateId; + + /// The linked principal slot of the template. + final String? principalType; + final String? principalId; + + /// The linked resource slot of the template. + final String? resourceType; + final String? resourceId; + + /// Type: integer (enforced=1, dry-run=0) + /// + /// authorization decision. + /// Dry-run policies are captured in the audit log but do not affect presentation of the + /// + /// The policy's enforcement level. + final int enforcementLevel; + const CedarPolicyTemplateLink( + {required this.id, + required this.policyId, + required this.templateId, + this.principalType, + this.principalId, + this.resourceType, + this.resourceId, + required this.enforcementLevel}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['policy_id'] = i0.Variable(policyId); + map['template_id'] = i0.Variable(templateId); + if (!nullToAbsent || principalType != null) { + map['principal_type'] = i0.Variable(principalType); + } + if (!nullToAbsent || principalId != null) { + map['principal_id'] = i0.Variable(principalId); + } + if (!nullToAbsent || resourceType != null) { + map['resource_type'] = i0.Variable(resourceType); + } + if (!nullToAbsent || resourceId != null) { + map['resource_id'] = i0.Variable(resourceId); + } + map['enforcement_level'] = i0.Variable(enforcementLevel); + return map; + } + + factory CedarPolicyTemplateLink.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarPolicyTemplateLink( + id: serializer.fromJson(json['id']), + policyId: serializer.fromJson(json['policy_id']), + templateId: serializer.fromJson(json['template_id']), + principalType: serializer.fromJson(json['principal_type']), + principalId: serializer.fromJson(json['principal_id']), + resourceType: serializer.fromJson(json['resource_type']), + resourceId: serializer.fromJson(json['resource_id']), + enforcementLevel: serializer.fromJson(json['enforcement_level']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'policy_id': serializer.toJson(policyId), + 'template_id': serializer.toJson(templateId), + 'principal_type': serializer.toJson(principalType), + 'principal_id': serializer.toJson(principalId), + 'resource_type': serializer.toJson(resourceType), + 'resource_id': serializer.toJson(resourceId), + 'enforcement_level': serializer.toJson(enforcementLevel), + }; + } + + i1.CedarPolicyTemplateLink copyWith( + {String? id, + String? policyId, + String? templateId, + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + int? enforcementLevel}) => + i1.CedarPolicyTemplateLink( + id: id ?? this.id, + policyId: policyId ?? this.policyId, + templateId: templateId ?? this.templateId, + principalType: + principalType.present ? principalType.value : this.principalType, + principalId: principalId.present ? principalId.value : this.principalId, + resourceType: + resourceType.present ? resourceType.value : this.resourceType, + resourceId: resourceId.present ? resourceId.value : this.resourceId, + enforcementLevel: enforcementLevel ?? this.enforcementLevel, + ); + CedarPolicyTemplateLink copyWithCompanion( + i1.CedarPolicyTemplateLinksCompanion data) { + return CedarPolicyTemplateLink( + id: data.id.present ? data.id.value : this.id, + policyId: data.policyId.present ? data.policyId.value : this.policyId, + templateId: + data.templateId.present ? data.templateId.value : this.templateId, + principalType: data.principalType.present + ? data.principalType.value + : this.principalType, + principalId: + data.principalId.present ? data.principalId.value : this.principalId, + resourceType: data.resourceType.present + ? data.resourceType.value + : this.resourceType, + resourceId: + data.resourceId.present ? data.resourceId.value : this.resourceId, + enforcementLevel: data.enforcementLevel.present + ? data.enforcementLevel.value + : this.enforcementLevel, + ); + } + + @override + String toString() { + return (StringBuffer('CedarPolicyTemplateLink(') + ..write('id: $id, ') + ..write('policyId: $policyId, ') + ..write('templateId: $templateId, ') + ..write('principalType: $principalType, ') + ..write('principalId: $principalId, ') + ..write('resourceType: $resourceType, ') + ..write('resourceId: $resourceId, ') + ..write('enforcementLevel: $enforcementLevel') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, policyId, templateId, principalType, + principalId, resourceType, resourceId, enforcementLevel); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarPolicyTemplateLink && + other.id == this.id && + other.policyId == this.policyId && + other.templateId == this.templateId && + other.principalType == this.principalType && + other.principalId == this.principalId && + other.resourceType == this.resourceType && + other.resourceId == this.resourceId && + other.enforcementLevel == this.enforcementLevel); +} + +class CedarPolicyTemplateLinksCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value policyId; + final i0.Value templateId; + final i0.Value principalType; + final i0.Value principalId; + final i0.Value resourceType; + final i0.Value resourceId; + final i0.Value enforcementLevel; + final i0.Value rowid; + const CedarPolicyTemplateLinksCompanion({ + this.id = const i0.Value.absent(), + this.policyId = const i0.Value.absent(), + this.templateId = const i0.Value.absent(), + this.principalType = const i0.Value.absent(), + this.principalId = const i0.Value.absent(), + this.resourceType = const i0.Value.absent(), + this.resourceId = const i0.Value.absent(), + this.enforcementLevel = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CedarPolicyTemplateLinksCompanion.insert({ + required String id, + required String policyId, + required String templateId, + this.principalType = const i0.Value.absent(), + this.principalId = const i0.Value.absent(), + this.resourceType = const i0.Value.absent(), + this.resourceId = const i0.Value.absent(), + this.enforcementLevel = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : id = i0.Value(id), + policyId = i0.Value(policyId), + templateId = i0.Value(templateId); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? policyId, + i0.Expression? templateId, + i0.Expression? principalType, + i0.Expression? principalId, + i0.Expression? resourceType, + i0.Expression? resourceId, + i0.Expression? enforcementLevel, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (policyId != null) 'policy_id': policyId, + if (templateId != null) 'template_id': templateId, + if (principalType != null) 'principal_type': principalType, + if (principalId != null) 'principal_id': principalId, + if (resourceType != null) 'resource_type': resourceType, + if (resourceId != null) 'resource_id': resourceId, + if (enforcementLevel != null) 'enforcement_level': enforcementLevel, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CedarPolicyTemplateLinksCompanion copyWith( + {i0.Value? id, + i0.Value? policyId, + i0.Value? templateId, + i0.Value? principalType, + i0.Value? principalId, + i0.Value? resourceType, + i0.Value? resourceId, + i0.Value? enforcementLevel, + i0.Value? rowid}) { + return i1.CedarPolicyTemplateLinksCompanion( + id: id ?? this.id, + policyId: policyId ?? this.policyId, + templateId: templateId ?? this.templateId, + principalType: principalType ?? this.principalType, + principalId: principalId ?? this.principalId, + resourceType: resourceType ?? this.resourceType, + resourceId: resourceId ?? this.resourceId, + enforcementLevel: enforcementLevel ?? this.enforcementLevel, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (policyId.present) { + map['policy_id'] = i0.Variable(policyId.value); + } + if (templateId.present) { + map['template_id'] = i0.Variable(templateId.value); + } + if (principalType.present) { + map['principal_type'] = i0.Variable(principalType.value); + } + if (principalId.present) { + map['principal_id'] = i0.Variable(principalId.value); + } + if (resourceType.present) { + map['resource_type'] = i0.Variable(resourceType.value); + } + if (resourceId.present) { + map['resource_id'] = i0.Variable(resourceId.value); + } + if (enforcementLevel.present) { + map['enforcement_level'] = i0.Variable(enforcementLevel.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarPolicyTemplateLinksCompanion(') + ..write('id: $id, ') + ..write('policyId: $policyId, ') + ..write('templateId: $templateId, ') + ..write('principalType: $principalType, ') + ..write('principalId: $principalId, ') + ..write('resourceType: $resourceType, ') + ..write('resourceId: $resourceId, ') + ..write('enforcementLevel: $enforcementLevel, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CedarPolicyTemplateLinksCreateCompanionBuilder + = i1.CedarPolicyTemplateLinksCompanion Function({ + required String id, + required String policyId, + required String templateId, + i0.Value principalType, + i0.Value principalId, + i0.Value resourceType, + i0.Value resourceId, + i0.Value enforcementLevel, + i0.Value rowid, +}); +typedef $CedarPolicyTemplateLinksUpdateCompanionBuilder + = i1.CedarPolicyTemplateLinksCompanion Function({ + i0.Value id, + i0.Value policyId, + i0.Value templateId, + i0.Value principalType, + i0.Value principalId, + i0.Value resourceType, + i0.Value resourceId, + i0.Value enforcementLevel, + i0.Value rowid, +}); + +class $CedarPolicyTemplateLinksFilterComposer extends i0 + .FilterComposer { + $CedarPolicyTemplateLinksFilterComposer(super.$state); + i0.ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get policyId => $state.composableBuilder( + column: $state.table.policyId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get templateId => $state.composableBuilder( + column: $state.table.templateId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get principalType => $state.composableBuilder( + column: $state.table.principalType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get principalId => $state.composableBuilder( + column: $state.table.principalId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get resourceType => $state.composableBuilder( + column: $state.table.resourceType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get resourceId => $state.composableBuilder( + column: $state.table.resourceId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get enforcementLevel => $state.composableBuilder( + column: $state.table.enforcementLevel, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CedarPolicyTemplateLinksOrderingComposer extends i0 + .OrderingComposer { + $CedarPolicyTemplateLinksOrderingComposer(super.$state); + i0.ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get policyId => $state.composableBuilder( + column: $state.table.policyId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get templateId => $state.composableBuilder( + column: $state.table.templateId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get principalType => $state.composableBuilder( + column: $state.table.principalType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get principalId => $state.composableBuilder( + column: $state.table.principalId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resourceType => $state.composableBuilder( + column: $state.table.resourceType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resourceId => $state.composableBuilder( + column: $state.table.resourceId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get enforcementLevel => $state.composableBuilder( + column: $state.table.enforcementLevel, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarPolicyTemplateLinksTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarPolicyTemplateLinks, + i1.CedarPolicyTemplateLink, + i1.$CedarPolicyTemplateLinksFilterComposer, + i1.$CedarPolicyTemplateLinksOrderingComposer, + $CedarPolicyTemplateLinksCreateCompanionBuilder, + $CedarPolicyTemplateLinksUpdateCompanionBuilder, + ( + i1.CedarPolicyTemplateLink, + i0.BaseReferences + ), + i1.CedarPolicyTemplateLink, + i0.PrefetchHooks Function()> { + $CedarPolicyTemplateLinksTableManager( + i0.GeneratedDatabase db, i1.CedarPolicyTemplateLinks table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: i1.$CedarPolicyTemplateLinksFilterComposer( + i0.ComposerState(db, table)), + orderingComposer: i1.$CedarPolicyTemplateLinksOrderingComposer( + i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value id = const i0.Value.absent(), + i0.Value policyId = const i0.Value.absent(), + i0.Value templateId = const i0.Value.absent(), + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + i0.Value enforcementLevel = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPolicyTemplateLinksCompanion( + id: id, + policyId: policyId, + templateId: templateId, + principalType: principalType, + principalId: principalId, + resourceType: resourceType, + resourceId: resourceId, + enforcementLevel: enforcementLevel, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String policyId, + required String templateId, + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + i0.Value enforcementLevel = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CedarPolicyTemplateLinksCompanion.insert( + id: id, + policyId: policyId, + templateId: templateId, + principalType: principalType, + principalId: principalId, + resourceType: resourceType, + resourceId: resourceId, + enforcementLevel: enforcementLevel, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarPolicyTemplateLinksProcessedTableManager + = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarPolicyTemplateLinks, + i1.CedarPolicyTemplateLink, + i1.$CedarPolicyTemplateLinksFilterComposer, + i1.$CedarPolicyTemplateLinksOrderingComposer, + $CedarPolicyTemplateLinksCreateCompanionBuilder, + $CedarPolicyTemplateLinksUpdateCompanionBuilder, + ( + i1.CedarPolicyTemplateLink, + i0.BaseReferences + ), + i1.CedarPolicyTemplateLink, + i0.PrefetchHooks Function()>; +i0.Index get cedarPolicyTemplateLinksFkTemplateIdIdx => i0.Index( + 'cedar_policy_template_links_fk_template_id_idx', + 'CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_template_id_idx ON cedar_policy_template_links (template_id)'); +i0.Index get cedarPolicyTemplateLinksFkPrincipalIdx => i0.Index( + 'cedar_policy_template_links_fk_principal_idx', + 'CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_principal_idx ON cedar_policy_template_links (principal_type, principal_id)'); +i0.Index get cedarPolicyTemplateLinksFkResourceIdx => i0.Index( + 'cedar_policy_template_links_fk_resource_idx', + 'CREATE INDEX IF NOT EXISTS cedar_policy_template_links_fk_resource_idx ON cedar_policy_template_links (resource_type, resource_id)'); + +class CedarAuthorizationLogs extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CedarAuthorizationLogs(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn rowid = i0.GeneratedColumn( + 'rowid', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT'); + late final i0.GeneratedColumn createTime = + i0.GeneratedColumn( + 'create_time', aliasedName, false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn expireTime = + i0.GeneratedColumn('expire_time', aliasedName, true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn principalType = + i0.GeneratedColumn('principal_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn principalId = + i0.GeneratedColumn('principal_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn actionType = i0.GeneratedColumn( + 'action_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn actionId = i0.GeneratedColumn( + 'action_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn resourceType = + i0.GeneratedColumn('resource_type', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn resourceId = i0.GeneratedColumn( + 'resource_id', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumnWithTypeConverter, String> + contextJson = i0.GeneratedColumn( + 'context_json', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'{}\'', + defaultValue: const i0.CustomExpression('\'{}\'')) + .withConverter>( + i1.CedarAuthorizationLogs.$convertercontextJson); + late final i0.GeneratedColumn decision = i0.GeneratedColumn( + 'decision', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumnWithTypeConverter, String> + reasonsJson = i0.GeneratedColumn( + 'reasons_json', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'[]\'', + defaultValue: const i0.CustomExpression('\'[]\'')) + .withConverter>( + i1.CedarAuthorizationLogs.$converterreasonsJson); + late final i0.GeneratedColumnWithTypeConverter + errorsJson = i0.GeneratedColumn('errors_json', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'[]\'', + defaultValue: const i0.CustomExpression('\'[]\'')) + .withConverter( + i1.CedarAuthorizationLogs.$convertererrorsJson); + @override + List get $columns => [ + rowid, + createTime, + expireTime, + principalType, + principalId, + actionType, + actionId, + resourceType, + resourceId, + contextJson, + decision, + reasonsJson, + errorsJson + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cedar_authorization_logs'; + @override + Set get $primaryKey => {rowid}; + @override + i1.CedarAuthorizationLog map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CedarAuthorizationLog( + rowid: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}rowid'])!, + createTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}create_time'])!, + expireTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}expire_time']), + principalType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}principal_type']), + principalId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}principal_id']), + actionType: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}action_type']), + actionId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}action_id']), + resourceType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}resource_type']), + resourceId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}resource_id']), + contextJson: i1.CedarAuthorizationLogs.$convertercontextJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}context_json'])!), + decision: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}decision'])!, + reasonsJson: i1.CedarAuthorizationLogs.$converterreasonsJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}reasons_json'])!), + errorsJson: i1.CedarAuthorizationLogs.$convertererrorsJson.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}errors_json'])!), + ); + } + + @override + CedarAuthorizationLogs createAlias(String alias) { + return CedarAuthorizationLogs(attachedDatabase, alias); + } + + static i0.TypeConverter, String> $convertercontextJson = + const i3.CedarAttributesConverter(); + static i0.TypeConverter, String> $converterreasonsJson = + const i3.CedarAuthorizationReasonsConverter(); + static i0.TypeConverter $convertererrorsJson = + const i3.CedarAuthorizationErrorsConverter(); + @override + bool get dontWriteConstraints => true; +} + +class CedarAuthorizationLog extends i0.DataClass + implements i0.Insertable { + final int rowid; + + /// Type: unixepoch.subsec + /// + /// The time the audit log entry was created. + final DateTime createTime; + + /// Type: unixepoch | null + /// + /// The log's expiration time if a TTL has been set. + final DateTime? expireTime; + + /// Type: string + /// + /// The requesting principal's entity type. + final String? principalType; + + /// Type: string + /// + /// The requesting principal's entity ID. + final String? principalId; + + /// Type: string + /// + /// The requested action's entity type. + final String? actionType; + + /// Type: string + /// + /// The requested action's entity ID. + final String? actionId; + + /// Type: string + /// + /// The requested resource's entity type. + final String? resourceType; + + /// Type: string + /// + /// The requested resource's entity ID. + final String? resourceId; + + /// JSON: `{ [key: string]: string }` + /// Type: JSON + /// + /// The context of the request + final Map contextJson; + + /// Type: bool (true=allow, false=deny) + /// + /// The authorization decision. + final bool decision; + + /// JSON: []string + /// Type: JSON + /// + /// The reasons for the authorization decision. + final List reasonsJson; + + /// ``` + /// ] + /// } + /// "message": "string" + /// "policy": "policy_id", + /// { + /// [ + /// ```json + /// JSON: + /// Type: JSON + /// + /// The errors encountered during the authorization decision. + final i6.AuthorizationErrors errorsJson; + const CedarAuthorizationLog( + {required this.rowid, + required this.createTime, + this.expireTime, + this.principalType, + this.principalId, + this.actionType, + this.actionId, + this.resourceType, + this.resourceId, + required this.contextJson, + required this.decision, + required this.reasonsJson, + required this.errorsJson}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['rowid'] = i0.Variable(rowid); + map['create_time'] = i0.Variable(createTime); + if (!nullToAbsent || expireTime != null) { + map['expire_time'] = i0.Variable(expireTime); + } + if (!nullToAbsent || principalType != null) { + map['principal_type'] = i0.Variable(principalType); + } + if (!nullToAbsent || principalId != null) { + map['principal_id'] = i0.Variable(principalId); + } + if (!nullToAbsent || actionType != null) { + map['action_type'] = i0.Variable(actionType); + } + if (!nullToAbsent || actionId != null) { + map['action_id'] = i0.Variable(actionId); + } + if (!nullToAbsent || resourceType != null) { + map['resource_type'] = i0.Variable(resourceType); + } + if (!nullToAbsent || resourceId != null) { + map['resource_id'] = i0.Variable(resourceId); + } + { + map['context_json'] = i0.Variable( + i1.CedarAuthorizationLogs.$convertercontextJson.toSql(contextJson)); + } + map['decision'] = i0.Variable(decision); + { + map['reasons_json'] = i0.Variable( + i1.CedarAuthorizationLogs.$converterreasonsJson.toSql(reasonsJson)); + } + { + map['errors_json'] = i0.Variable( + i1.CedarAuthorizationLogs.$convertererrorsJson.toSql(errorsJson)); + } + return map; + } + + factory CedarAuthorizationLog.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CedarAuthorizationLog( + rowid: serializer.fromJson(json['rowid']), + createTime: serializer.fromJson(json['create_time']), + expireTime: serializer.fromJson(json['expire_time']), + principalType: serializer.fromJson(json['principal_type']), + principalId: serializer.fromJson(json['principal_id']), + actionType: serializer.fromJson(json['action_type']), + actionId: serializer.fromJson(json['action_id']), + resourceType: serializer.fromJson(json['resource_type']), + resourceId: serializer.fromJson(json['resource_id']), + contextJson: + serializer.fromJson>(json['context_json']), + decision: serializer.fromJson(json['decision']), + reasonsJson: serializer.fromJson>(json['reasons_json']), + errorsJson: + serializer.fromJson(json['errors_json']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'rowid': serializer.toJson(rowid), + 'create_time': serializer.toJson(createTime), + 'expire_time': serializer.toJson(expireTime), + 'principal_type': serializer.toJson(principalType), + 'principal_id': serializer.toJson(principalId), + 'action_type': serializer.toJson(actionType), + 'action_id': serializer.toJson(actionId), + 'resource_type': serializer.toJson(resourceType), + 'resource_id': serializer.toJson(resourceId), + 'context_json': serializer.toJson>(contextJson), + 'decision': serializer.toJson(decision), + 'reasons_json': serializer.toJson>(reasonsJson), + 'errors_json': serializer.toJson(errorsJson), + }; + } + + i1.CedarAuthorizationLog copyWith( + {int? rowid, + DateTime? createTime, + i0.Value expireTime = const i0.Value.absent(), + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value actionType = const i0.Value.absent(), + i0.Value actionId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + Map? contextJson, + bool? decision, + List? reasonsJson, + i6.AuthorizationErrors? errorsJson}) => + i1.CedarAuthorizationLog( + rowid: rowid ?? this.rowid, + createTime: createTime ?? this.createTime, + expireTime: expireTime.present ? expireTime.value : this.expireTime, + principalType: + principalType.present ? principalType.value : this.principalType, + principalId: principalId.present ? principalId.value : this.principalId, + actionType: actionType.present ? actionType.value : this.actionType, + actionId: actionId.present ? actionId.value : this.actionId, + resourceType: + resourceType.present ? resourceType.value : this.resourceType, + resourceId: resourceId.present ? resourceId.value : this.resourceId, + contextJson: contextJson ?? this.contextJson, + decision: decision ?? this.decision, + reasonsJson: reasonsJson ?? this.reasonsJson, + errorsJson: errorsJson ?? this.errorsJson, + ); + CedarAuthorizationLog copyWithCompanion( + i1.CedarAuthorizationLogsCompanion data) { + return CedarAuthorizationLog( + rowid: data.rowid.present ? data.rowid.value : this.rowid, + createTime: + data.createTime.present ? data.createTime.value : this.createTime, + expireTime: + data.expireTime.present ? data.expireTime.value : this.expireTime, + principalType: data.principalType.present + ? data.principalType.value + : this.principalType, + principalId: + data.principalId.present ? data.principalId.value : this.principalId, + actionType: + data.actionType.present ? data.actionType.value : this.actionType, + actionId: data.actionId.present ? data.actionId.value : this.actionId, + resourceType: data.resourceType.present + ? data.resourceType.value + : this.resourceType, + resourceId: + data.resourceId.present ? data.resourceId.value : this.resourceId, + contextJson: + data.contextJson.present ? data.contextJson.value : this.contextJson, + decision: data.decision.present ? data.decision.value : this.decision, + reasonsJson: + data.reasonsJson.present ? data.reasonsJson.value : this.reasonsJson, + errorsJson: + data.errorsJson.present ? data.errorsJson.value : this.errorsJson, + ); + } + + @override + String toString() { + return (StringBuffer('CedarAuthorizationLog(') + ..write('rowid: $rowid, ') + ..write('createTime: $createTime, ') + ..write('expireTime: $expireTime, ') + ..write('principalType: $principalType, ') + ..write('principalId: $principalId, ') + ..write('actionType: $actionType, ') + ..write('actionId: $actionId, ') + ..write('resourceType: $resourceType, ') + ..write('resourceId: $resourceId, ') + ..write('contextJson: $contextJson, ') + ..write('decision: $decision, ') + ..write('reasonsJson: $reasonsJson, ') + ..write('errorsJson: $errorsJson') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + rowid, + createTime, + expireTime, + principalType, + principalId, + actionType, + actionId, + resourceType, + resourceId, + contextJson, + decision, + reasonsJson, + errorsJson); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CedarAuthorizationLog && + other.rowid == this.rowid && + other.createTime == this.createTime && + other.expireTime == this.expireTime && + other.principalType == this.principalType && + other.principalId == this.principalId && + other.actionType == this.actionType && + other.actionId == this.actionId && + other.resourceType == this.resourceType && + other.resourceId == this.resourceId && + other.contextJson == this.contextJson && + other.decision == this.decision && + other.reasonsJson == this.reasonsJson && + other.errorsJson == this.errorsJson); +} + +class CedarAuthorizationLogsCompanion + extends i0.UpdateCompanion { + final i0.Value rowid; + final i0.Value createTime; + final i0.Value expireTime; + final i0.Value principalType; + final i0.Value principalId; + final i0.Value actionType; + final i0.Value actionId; + final i0.Value resourceType; + final i0.Value resourceId; + final i0.Value> contextJson; + final i0.Value decision; + final i0.Value> reasonsJson; + final i0.Value errorsJson; + const CedarAuthorizationLogsCompanion({ + this.rowid = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.expireTime = const i0.Value.absent(), + this.principalType = const i0.Value.absent(), + this.principalId = const i0.Value.absent(), + this.actionType = const i0.Value.absent(), + this.actionId = const i0.Value.absent(), + this.resourceType = const i0.Value.absent(), + this.resourceId = const i0.Value.absent(), + this.contextJson = const i0.Value.absent(), + this.decision = const i0.Value.absent(), + this.reasonsJson = const i0.Value.absent(), + this.errorsJson = const i0.Value.absent(), + }); + CedarAuthorizationLogsCompanion.insert({ + this.rowid = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.expireTime = const i0.Value.absent(), + this.principalType = const i0.Value.absent(), + this.principalId = const i0.Value.absent(), + this.actionType = const i0.Value.absent(), + this.actionId = const i0.Value.absent(), + this.resourceType = const i0.Value.absent(), + this.resourceId = const i0.Value.absent(), + this.contextJson = const i0.Value.absent(), + required bool decision, + this.reasonsJson = const i0.Value.absent(), + this.errorsJson = const i0.Value.absent(), + }) : decision = i0.Value(decision); + static i0.Insertable custom({ + i0.Expression? rowid, + i0.Expression? createTime, + i0.Expression? expireTime, + i0.Expression? principalType, + i0.Expression? principalId, + i0.Expression? actionType, + i0.Expression? actionId, + i0.Expression? resourceType, + i0.Expression? resourceId, + i0.Expression? contextJson, + i0.Expression? decision, + i0.Expression? reasonsJson, + i0.Expression? errorsJson, + }) { + return i0.RawValuesInsertable({ + if (rowid != null) 'rowid': rowid, + if (createTime != null) 'create_time': createTime, + if (expireTime != null) 'expire_time': expireTime, + if (principalType != null) 'principal_type': principalType, + if (principalId != null) 'principal_id': principalId, + if (actionType != null) 'action_type': actionType, + if (actionId != null) 'action_id': actionId, + if (resourceType != null) 'resource_type': resourceType, + if (resourceId != null) 'resource_id': resourceId, + if (contextJson != null) 'context_json': contextJson, + if (decision != null) 'decision': decision, + if (reasonsJson != null) 'reasons_json': reasonsJson, + if (errorsJson != null) 'errors_json': errorsJson, + }); + } + + i1.CedarAuthorizationLogsCompanion copyWith( + {i0.Value? rowid, + i0.Value? createTime, + i0.Value? expireTime, + i0.Value? principalType, + i0.Value? principalId, + i0.Value? actionType, + i0.Value? actionId, + i0.Value? resourceType, + i0.Value? resourceId, + i0.Value>? contextJson, + i0.Value? decision, + i0.Value>? reasonsJson, + i0.Value? errorsJson}) { + return i1.CedarAuthorizationLogsCompanion( + rowid: rowid ?? this.rowid, + createTime: createTime ?? this.createTime, + expireTime: expireTime ?? this.expireTime, + principalType: principalType ?? this.principalType, + principalId: principalId ?? this.principalId, + actionType: actionType ?? this.actionType, + actionId: actionId ?? this.actionId, + resourceType: resourceType ?? this.resourceType, + resourceId: resourceId ?? this.resourceId, + contextJson: contextJson ?? this.contextJson, + decision: decision ?? this.decision, + reasonsJson: reasonsJson ?? this.reasonsJson, + errorsJson: errorsJson ?? this.errorsJson, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + if (createTime.present) { + map['create_time'] = i0.Variable(createTime.value); + } + if (expireTime.present) { + map['expire_time'] = i0.Variable(expireTime.value); + } + if (principalType.present) { + map['principal_type'] = i0.Variable(principalType.value); + } + if (principalId.present) { + map['principal_id'] = i0.Variable(principalId.value); + } + if (actionType.present) { + map['action_type'] = i0.Variable(actionType.value); + } + if (actionId.present) { + map['action_id'] = i0.Variable(actionId.value); + } + if (resourceType.present) { + map['resource_type'] = i0.Variable(resourceType.value); + } + if (resourceId.present) { + map['resource_id'] = i0.Variable(resourceId.value); + } + if (contextJson.present) { + map['context_json'] = i0.Variable(i1 + .CedarAuthorizationLogs.$convertercontextJson + .toSql(contextJson.value)); + } + if (decision.present) { + map['decision'] = i0.Variable(decision.value); + } + if (reasonsJson.present) { + map['reasons_json'] = i0.Variable(i1 + .CedarAuthorizationLogs.$converterreasonsJson + .toSql(reasonsJson.value)); + } + if (errorsJson.present) { + map['errors_json'] = i0.Variable(i1 + .CedarAuthorizationLogs.$convertererrorsJson + .toSql(errorsJson.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CedarAuthorizationLogsCompanion(') + ..write('rowid: $rowid, ') + ..write('createTime: $createTime, ') + ..write('expireTime: $expireTime, ') + ..write('principalType: $principalType, ') + ..write('principalId: $principalId, ') + ..write('actionType: $actionType, ') + ..write('actionId: $actionId, ') + ..write('resourceType: $resourceType, ') + ..write('resourceId: $resourceId, ') + ..write('contextJson: $contextJson, ') + ..write('decision: $decision, ') + ..write('reasonsJson: $reasonsJson, ') + ..write('errorsJson: $errorsJson') + ..write(')')) + .toString(); + } +} + +typedef $CedarAuthorizationLogsCreateCompanionBuilder + = i1.CedarAuthorizationLogsCompanion Function({ + i0.Value rowid, + i0.Value createTime, + i0.Value expireTime, + i0.Value principalType, + i0.Value principalId, + i0.Value actionType, + i0.Value actionId, + i0.Value resourceType, + i0.Value resourceId, + i0.Value> contextJson, + required bool decision, + i0.Value> reasonsJson, + i0.Value errorsJson, +}); +typedef $CedarAuthorizationLogsUpdateCompanionBuilder + = i1.CedarAuthorizationLogsCompanion Function({ + i0.Value rowid, + i0.Value createTime, + i0.Value expireTime, + i0.Value principalType, + i0.Value principalId, + i0.Value actionType, + i0.Value actionId, + i0.Value resourceType, + i0.Value resourceId, + i0.Value> contextJson, + i0.Value decision, + i0.Value> reasonsJson, + i0.Value errorsJson, +}); + +class $CedarAuthorizationLogsFilterComposer + extends i0.FilterComposer { + $CedarAuthorizationLogsFilterComposer(super.$state); + i0.ColumnFilters get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get principalType => $state.composableBuilder( + column: $state.table.principalType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get principalId => $state.composableBuilder( + column: $state.table.principalId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get actionType => $state.composableBuilder( + column: $state.table.actionType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get actionId => $state.composableBuilder( + column: $state.table.actionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get resourceType => $state.composableBuilder( + column: $state.table.resourceType, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get resourceId => $state.composableBuilder( + column: $state.table.resourceId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters, + Map, String> + get contextJson => $state.composableBuilder( + column: $state.table.contextJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get decision => $state.composableBuilder( + column: $state.table.decision, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters, List, String> + get reasonsJson => $state.composableBuilder( + column: $state.table.reasonsJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get errorsJson => $state.composableBuilder( + column: $state.table.errorsJson, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); +} + +class $CedarAuthorizationLogsOrderingComposer extends i0 + .OrderingComposer { + $CedarAuthorizationLogsOrderingComposer(super.$state); + i0.ColumnOrderings get rowid => $state.composableBuilder( + column: $state.table.rowid, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get expireTime => $state.composableBuilder( + column: $state.table.expireTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get principalType => $state.composableBuilder( + column: $state.table.principalType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get principalId => $state.composableBuilder( + column: $state.table.principalId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get actionType => $state.composableBuilder( + column: $state.table.actionType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get actionId => $state.composableBuilder( + column: $state.table.actionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resourceType => $state.composableBuilder( + column: $state.table.resourceType, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resourceId => $state.composableBuilder( + column: $state.table.resourceId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get contextJson => $state.composableBuilder( + column: $state.table.contextJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get decision => $state.composableBuilder( + column: $state.table.decision, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get reasonsJson => $state.composableBuilder( + column: $state.table.reasonsJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get errorsJson => $state.composableBuilder( + column: $state.table.errorsJson, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CedarAuthorizationLogsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CedarAuthorizationLogs, + i1.CedarAuthorizationLog, + i1.$CedarAuthorizationLogsFilterComposer, + i1.$CedarAuthorizationLogsOrderingComposer, + $CedarAuthorizationLogsCreateCompanionBuilder, + $CedarAuthorizationLogsUpdateCompanionBuilder, + ( + i1.CedarAuthorizationLog, + i0.BaseReferences + ), + i1.CedarAuthorizationLog, + i0.PrefetchHooks Function()> { + $CedarAuthorizationLogsTableManager( + i0.GeneratedDatabase db, i1.CedarAuthorizationLogs table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: i1.$CedarAuthorizationLogsFilterComposer( + i0.ComposerState(db, table)), + orderingComposer: i1.$CedarAuthorizationLogsOrderingComposer( + i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value expireTime = const i0.Value.absent(), + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value actionType = const i0.Value.absent(), + i0.Value actionId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + i0.Value> contextJson = + const i0.Value.absent(), + i0.Value decision = const i0.Value.absent(), + i0.Value> reasonsJson = const i0.Value.absent(), + i0.Value errorsJson = + const i0.Value.absent(), + }) => + i1.CedarAuthorizationLogsCompanion( + rowid: rowid, + createTime: createTime, + expireTime: expireTime, + principalType: principalType, + principalId: principalId, + actionType: actionType, + actionId: actionId, + resourceType: resourceType, + resourceId: resourceId, + contextJson: contextJson, + decision: decision, + reasonsJson: reasonsJson, + errorsJson: errorsJson, + ), + createCompanionCallback: ({ + i0.Value rowid = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value expireTime = const i0.Value.absent(), + i0.Value principalType = const i0.Value.absent(), + i0.Value principalId = const i0.Value.absent(), + i0.Value actionType = const i0.Value.absent(), + i0.Value actionId = const i0.Value.absent(), + i0.Value resourceType = const i0.Value.absent(), + i0.Value resourceId = const i0.Value.absent(), + i0.Value> contextJson = + const i0.Value.absent(), + required bool decision, + i0.Value> reasonsJson = const i0.Value.absent(), + i0.Value errorsJson = + const i0.Value.absent(), + }) => + i1.CedarAuthorizationLogsCompanion.insert( + rowid: rowid, + createTime: createTime, + expireTime: expireTime, + principalType: principalType, + principalId: principalId, + actionType: actionType, + actionId: actionId, + resourceType: resourceType, + resourceId: resourceId, + contextJson: contextJson, + decision: decision, + reasonsJson: reasonsJson, + errorsJson: errorsJson, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CedarAuthorizationLogsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CedarAuthorizationLogs, + i1.CedarAuthorizationLog, + i1.$CedarAuthorizationLogsFilterComposer, + i1.$CedarAuthorizationLogsOrderingComposer, + $CedarAuthorizationLogsCreateCompanionBuilder, + $CedarAuthorizationLogsUpdateCompanionBuilder, + ( + i1.CedarAuthorizationLog, + i0.BaseReferences + ), + i1.CedarAuthorizationLog, + i0.PrefetchHooks Function()>; + +class CedarDrift extends i4.ModularAccessor { + CedarDrift(i0.GeneratedDatabase db) : super(db); + i0.Selectable> getEntityClosure( + {required String type, required String id}) { + return customSelect( + 'WITH RECURSIVE parents AS (SELECT e.entity_type, e.entity_id, e.parent_type, e.parent_id, e.parent_json FROM cedar_relationships AS e WHERE e.entity_type = ?1 AND e.entity_id = ?2 UNION ALL SELECT p.entity_type, p.entity_id, p.parent_type, p.parent_id, p.parent_json FROM cedar_relationships AS p INNER JOIN parents AS a ON p.entity_id = a.parent_id AND p.entity_type = a.parent_type), entities AS (SELECT * FROM cedar_entities WHERE (entity_type, entity_id) IN (SELECT entity_type, entity_id FROM parents) OR (entity_type, entity_id) IN (SELECT parent_type, parent_id FROM parents) OR (entity_type, entity_id) = (?1, ?2)) SELECT json_group_array(json_object(\'uid\', entity_json, \'attrs\', json(attribute_json), \'parents\', (SELECT json_group_array(json(parent_json)) FROM parents AS p WHERE p.entity_type = e.entity_type AND p.entity_id = e.entity_id))) AS _c0 FROM entities AS e', + variables: [ + i0.Variable(type), + i0.Variable(id) + ], + readsFrom: { + cedarRelationships, + cedarEntities, + }).map((i0.QueryRow row) => const i3.CedarEntityClosureConverter() + .fromSql(row.read('_c0'))); + } + + i0.Selectable debugDumpCedar() { + return customSelect( + 'SELECT e.entity_type, e.entity_id, coalesce(r.parent_type, \'\') AS _c0, coalesce(r.parent_id, \'\') AS _c1 FROM cedar_entities AS e LEFT JOIN cedar_relationships AS r ON e.entity_type = r.entity_type AND e.entity_id = r.entity_id', + variables: [], + readsFrom: { + cedarEntities, + cedarRelationships, + }).map((i0.QueryRow row) => DebugDumpCedarResult( + entityType: row.read('entity_type'), + entityId: row.read('entity_id'), + coalescerparentType: row.read('_c0'), + coalescerparentId: row.read('_c1'), + )); + } + + Future createPolicy( + {required String id, + required String policyId, + required i5.Policy policy, + required int enforcementLevel}) { + return customInsert( + 'INSERT INTO cedar_policies (id, policy_id, policy, enforcement_level) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (policy_id) DO UPDATE SET policy = excluded.policy, enforcement_level = excluded.enforcement_level', + variables: [ + i0.Variable(id), + i0.Variable(policyId), + i0.Variable(i1.CedarPolicies.$converterpolicy.toSql(policy)), + i0.Variable(enforcementLevel) + ], + updates: {cedarPolicies}, + ); + } + + Future upsertPolicy( + {required String id, + required String policyId, + required i5.Policy policy, + required int enforcementLevel}) { + return customInsert( + 'INSERT INTO cedar_policies (id, policy_id, policy, enforcement_level) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (policy_id) DO UPDATE SET policy = excluded.policy, enforcement_level = excluded.enforcement_level', + variables: [ + i0.Variable(id), + i0.Variable(policyId), + i0.Variable(i1.CedarPolicies.$converterpolicy.toSql(policy)), + i0.Variable(enforcementLevel) + ], + updates: {cedarPolicies}, + ); + } + + Future createPolicyTemplate( + {required String id, + required String templateId, + required i5.Policy template}) { + return customInsert( + 'INSERT INTO cedar_policy_templates (id, template_id, template) VALUES (?1, ?2, ?3) ON CONFLICT (template_id) DO UPDATE SET template = excluded.template', + variables: [ + i0.Variable(id), + i0.Variable(templateId), + i0.Variable( + i1.CedarPolicyTemplates.$convertertemplate.toSql(template)) + ], + updates: {cedarPolicyTemplates}, + ); + } + + Future upsertPolicyTemplate( + {required String id, + required String templateId, + required i5.Policy template}) { + return customInsert( + 'INSERT INTO cedar_policy_templates (id, template_id, template) VALUES (?1, ?2, ?3) ON CONFLICT (template_id) DO UPDATE SET template = excluded.template', + variables: [ + i0.Variable(id), + i0.Variable(templateId), + i0.Variable( + i1.CedarPolicyTemplates.$convertertemplate.toSql(template)) + ], + updates: {cedarPolicyTemplates}, + ); + } + + Future upsertPolicyTemplateLink( + {required String id, + required String policyId, + required String templateId, + String? principalType, + String? principalId, + String? resourceType, + String? resourceId, + required int enforcementLevel}) { + return customInsert( + 'INSERT INTO cedar_policy_template_links (id, policy_id, template_id, principal_type, principal_id, resource_type, resource_id, enforcement_level) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) ON CONFLICT (policy_id) DO UPDATE SET principal_type = excluded.principal_type, principal_id = excluded.principal_id, resource_type = excluded.resource_type, resource_id = excluded.resource_id, enforcement_level = excluded.enforcement_level', + variables: [ + i0.Variable(id), + i0.Variable(policyId), + i0.Variable(templateId), + i0.Variable(principalType), + i0.Variable(principalId), + i0.Variable(resourceType), + i0.Variable(resourceId), + i0.Variable(enforcementLevel) + ], + updates: {cedarPolicyTemplateLinks}, + ); + } + + Future linkPolicyTemplate( + {required String id, + required String policyId, + required String templateId, + String? principalType, + String? principalId, + String? resourceType, + String? resourceId, + required int enforcementLevel}) { + return customInsert( + 'INSERT INTO cedar_policy_template_links (id, policy_id, template_id, principal_type, principal_id, resource_type, resource_id, enforcement_level) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)', + variables: [ + i0.Variable(id), + i0.Variable(policyId), + i0.Variable(templateId), + i0.Variable(principalType), + i0.Variable(principalId), + i0.Variable(resourceType), + i0.Variable(resourceId), + i0.Variable(enforcementLevel) + ], + updates: {cedarPolicyTemplateLinks}, + ); + } + + i0.Selectable listEffectivePolicies() { + return customSelect( + 'WITH template_links AS (SELECT l.id, l.policy_id, t.template, l.principal_type, l.principal_id, l.resource_type, l.resource_id, l.enforcement_level FROM cedar_policy_templates AS t INNER JOIN cedar_policy_template_links AS l ON l.template_id = t.id) SELECT id, template_id AS policy_id, template AS policy, NULL AS principal_type, NULL AS principal_id, NULL AS resource_type, NULL AS resource_id, NULL AS enforcement_level FROM cedar_policy_templates UNION ALL SELECT id, policy_id, template AS policy, principal_type, principal_id, resource_type, resource_id, enforcement_level FROM template_links UNION ALL SELECT id, policy_id, policy, NULL AS principal_type, NULL AS principal_id, NULL AS resource_type, NULL AS resource_id, enforcement_level FROM cedar_policies', + variables: [], + readsFrom: { + cedarPolicyTemplateLinks, + cedarPolicyTemplates, + cedarPolicies, + }).map((i0.QueryRow row) => ListEffectivePoliciesResult( + id: row.read('id'), + policyId: row.read('policy_id'), + policy: i1.CedarPolicyTemplates.$convertertemplate + .fromSql(row.read('policy')), + principalType: row.readNullable('principal_type'), + principalId: row.readNullable('principal_id'), + resourceType: row.readNullable('resource_type'), + resourceId: row.readNullable('resource_id'), + enforcementLevel: row.readNullable('enforcement_level'), + )); + } + + Future updatePolicy( + {String? policy, int? enforcementLevel, required String policyId}) { + return customUpdate( + 'UPDATE cedar_policies SET policy = coalesce(?1, policy), enforcement_level = coalesce(?2, enforcement_level) WHERE id = ?3 OR policy_id = ?3', + variables: [ + i0.Variable(policy), + i0.Variable(enforcementLevel), + i0.Variable(policyId) + ], + updates: {cedarPolicies}, + updateKind: i0.UpdateKind.update, + ); + } + + Future updatePolicyTemplate( + {String? template, required String templateId}) { + return customUpdate( + 'UPDATE cedar_policy_templates SET template = coalesce(?1, template) WHERE id = ?2 OR template_id = ?2', + variables: [ + i0.Variable(template), + i0.Variable(templateId) + ], + updates: {cedarPolicyTemplates}, + updateKind: i0.UpdateKind.update, + ); + } + + Future updatePolicyTemplateLink( + {int? enforcementLevel, required String policyId}) { + return customUpdate( + 'UPDATE cedar_policy_template_links SET enforcement_level = coalesce(?1, enforcement_level) WHERE id = ?2 OR policy_id = ?2', + variables: [ + i0.Variable(enforcementLevel), + i0.Variable(policyId) + ], + updates: {cedarPolicyTemplateLinks}, + updateKind: i0.UpdateKind.update, + ); + } + + i7.Future> deletePolicy({required String policyId}) { + return customWriteReturning( + 'DELETE FROM cedar_policies WHERE id = ?1 OR policy_id = ?1 RETURNING *', + variables: [i0.Variable(policyId)], + updates: {cedarPolicies}, + updateKind: i0.UpdateKind.delete) + .then((rows) => Future.wait(rows.map(cedarPolicies.mapFromRow))); + } + + i7.Future> deletePolicyTemplate( + {required String templateId}) { + return customWriteReturning( + 'DELETE FROM cedar_policy_templates WHERE id = ?1 OR template_id = ?1 RETURNING *', + variables: [i0.Variable(templateId)], + updates: {cedarPolicyTemplates}, + updateKind: i0.UpdateKind.delete) + .then((rows) => Future.wait(rows.map(cedarPolicyTemplates.mapFromRow))); + } + + i7.Future> deletePolicyTemplateLink( + {required String policyId}) { + return customWriteReturning( + 'DELETE FROM cedar_policy_template_links WHERE id = ?1 OR policy_id = ?1 RETURNING *', + variables: [i0.Variable(policyId)], + updates: {cedarPolicyTemplateLinks}, + updateKind: i0.UpdateKind.delete) + .then((rows) => + Future.wait(rows.map(cedarPolicyTemplateLinks.mapFromRow))); + } + + Future recordAuthorization( + {String? principalType, + String? principalId, + String? actionType, + String? actionId, + String? resourceType, + String? resourceId, + required Map contextJson, + required bool decision, + required List reasonsJson, + required i6.AuthorizationErrors errorsJson}) { + return customInsert( + 'INSERT INTO cedar_authorization_logs (principal_type, principal_id, action_type, action_id, resource_type, resource_id, context_json, decision, reasons_json, errors_json) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)', + variables: [ + i0.Variable(principalType), + i0.Variable(principalId), + i0.Variable(actionType), + i0.Variable(actionId), + i0.Variable(resourceType), + i0.Variable(resourceId), + i0.Variable( + i1.CedarAuthorizationLogs.$convertercontextJson.toSql(contextJson)), + i0.Variable(decision), + i0.Variable( + i1.CedarAuthorizationLogs.$converterreasonsJson.toSql(reasonsJson)), + i0.Variable( + i1.CedarAuthorizationLogs.$convertererrorsJson.toSql(errorsJson)) + ], + updates: {cedarAuthorizationLogs}, + ); + } + + Future createRelationship( + {required String entityType, + required String entityId, + required String parentType, + required String parentId}) { + return customInsert( + 'INSERT INTO cedar_relationships (entity_type, entity_id, parent_type, parent_id) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (entity_type, entity_id, parent_type, parent_id) DO NOTHING', + variables: [ + i0.Variable(entityType), + i0.Variable(entityId), + i0.Variable(parentType), + i0.Variable(parentId) + ], + updates: {cedarRelationships}, + ); + } + + i7.Future> createEntity( + {required String entityType, + required String entityId, + required Map attributeJson}) { + return customWriteReturning( + 'INSERT INTO cedar_entities (entity_type, entity_id, attribute_json) VALUES (?1, ?2, ?3) ON CONFLICT (entity_type, entity_id) DO UPDATE SET attribute_json = excluded.attribute_json RETURNING *', + variables: [ + i0.Variable(entityType), + i0.Variable(entityId), + i0.Variable( + i1.CedarEntities.$converterattributeJson.toSql(attributeJson)) + ], + updates: { + cedarEntities + }).then((rows) => Future.wait(rows.map(cedarEntities.mapFromRow))); + } + + Future createType({required String fqn}) { + return customInsert( + 'INSERT INTO cedar_types (fqn) VALUES (?1) ON CONFLICT (fqn) DO NOTHING', + variables: [i0.Variable(fqn)], + updates: {cedarTypes}, + ); + } + + i0.Selectable getEntity( + {required String entityType, required String entityId}) { + return customSelect( + 'SELECT * FROM cedar_entities WHERE entity_type = ?1 AND entity_id = ?2', + variables: [ + i0.Variable(entityType), + i0.Variable(entityId) + ], + readsFrom: { + cedarEntities, + }).asyncMap(cedarEntities.mapFromRow); + } + + i0.Selectable getRelationship( + {required String entityType, required String entityId}) { + return customSelect( + 'SELECT * FROM cedar_relationships WHERE entity_type = ?1 AND entity_id = ?2', + variables: [ + i0.Variable(entityType), + i0.Variable(entityId) + ], + readsFrom: { + cedarRelationships, + }).asyncMap(cedarRelationships.mapFromRow); + } + + Future updateEntity( + {String? attributeJson, + required String entityType, + required String entityId}) { + return customUpdate( + 'UPDATE cedar_entities SET attribute_json = coalesce(?1, attribute_json) WHERE entity_type = ?2 AND entity_id = ?3', + variables: [ + i0.Variable(attributeJson), + i0.Variable(entityType), + i0.Variable(entityId) + ], + updates: {cedarEntities}, + updateKind: i0.UpdateKind.update, + ); + } + + i1.CedarRelationships get cedarRelationships => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_relationships'); + i1.CedarEntities get cedarEntities => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_entities'); + i1.CedarPolicies get cedarPolicies => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_policies'); + i1.CedarPolicyTemplates get cedarPolicyTemplates => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_policy_templates'); + i1.CedarPolicyTemplateLinks get cedarPolicyTemplateLinks => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet( + 'cedar_policy_template_links'); + i1.CedarAuthorizationLogs get cedarAuthorizationLogs => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_authorization_logs'); + i1.CedarTypes get cedarTypes => i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('cedar_types'); +} + +class DebugDumpCedarResult { + final String entityType; + final String entityId; + final String coalescerparentType; + final String coalescerparentId; + DebugDumpCedarResult({ + required this.entityType, + required this.entityId, + required this.coalescerparentType, + required this.coalescerparentId, + }); +} + +class ListEffectivePoliciesResult { + final String id; + final String policyId; + final i5.Policy policy; + final String? principalType; + final String? principalId; + final String? resourceType; + final String? resourceId; + final int? enforcementLevel; + ListEffectivePoliciesResult({ + required this.id, + required this.policyId, + required this.policy, + this.principalType, + this.principalId, + this.resourceType, + this.resourceId, + this.enforcementLevel, + }); +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/converters/ast_converters.dart b/services/celest_cloud_auth/lib/src/database/schema/converters/ast_converters.dart new file mode 100644 index 00000000..506a438e --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/converters/ast_converters.dart @@ -0,0 +1,54 @@ +import 'package:celest_ast/celest_ast.dart' as ast; +import 'package:celest_ast/src/proto/celest/ast/v1/resolved_ast.pb.dart' as pb; +import 'package:drift/drift.dart'; + +final class ResolvedProjectConverter + implements TypeConverter { + const ResolvedProjectConverter(); + + @override + ast.ResolvedProject fromSql(Uint8List fromDb) { + return ast.ResolvedProject.fromProto( + pb.ResolvedProject()..mergeFromBuffer(fromDb), + ); + } + + @override + Uint8List toSql(ast.ResolvedProject value) { + return value.toProto().writeToBuffer(); + } +} + +final class ResolvedApiConverter + implements TypeConverter { + const ResolvedApiConverter(); + + @override + ast.ResolvedApi fromSql(Uint8List fromDb) { + return ast.ResolvedApi.fromProto( + pb.ResolvedApi()..mergeFromBuffer(fromDb), + ); + } + + @override + Uint8List toSql(ast.ResolvedApi value) { + return value.toProto().writeToBuffer(); + } +} + +final class ResolvedFunctionConverter + implements TypeConverter { + const ResolvedFunctionConverter(); + + @override + ast.ResolvedCloudFunction fromSql(Uint8List fromDb) { + return ast.ResolvedCloudFunction.fromProto( + pb.ResolvedFunction()..mergeFromBuffer(fromDb), + ); + } + + @override + Uint8List toSql(ast.ResolvedCloudFunction value) { + return value.toProto().writeToBuffer(); + } +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/converters/auth_converters.dart b/services/celest_cloud_auth/lib/src/database/schema/converters/auth_converters.dart new file mode 100644 index 00000000..d48aaeba --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/converters/auth_converters.dart @@ -0,0 +1,73 @@ +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_cloud/src/proto.dart' as pb; +import 'package:drift/drift.dart'; +import 'package:protobuf/protobuf.dart'; + +final typeRegistry = TypeRegistry([ + pb.AuthenticationSuccess(), + pb.AuthenticationFactor(), + pb.AuthenticationFactorEmailOtp(), + pb.AuthenticationFactorSmsOtp(), + pb.Session(), + pb.SessionClient(), + pb.SessionCallbacks(), +]); + +final class SessionClientConverter + implements TypeConverter { + const SessionClientConverter(); + + @override + SessionClient fromSql(Uint8List fromDb) { + return SessionClient.fromProto(pb.SessionClient.fromBuffer(fromDb)); + } + + @override + Uint8List toSql(SessionClient value) { + return value.toProto().writeToBuffer(); + } +} + +final class AuthenticationFactorConverter + implements TypeConverter { + const AuthenticationFactorConverter(); + + @override + AuthenticationFactor fromSql(Uint8List fromDb) { + return AuthenticationFactor.fromProto( + pb.AuthenticationFactor.fromBuffer(fromDb), + ); + } + + @override + Uint8List toSql(AuthenticationFactor value) { + return value.toProto().writeToBuffer(); + } +} + +final class SessionStateConverter + implements TypeConverter { + const SessionStateConverter(); + + @override + SessionState fromSql(Uint8List fromDb) { + final message = pb.Any.fromBuffer(fromDb); + final nextStep = pb.AuthenticationStep(); + if (message.canUnpackInto(nextStep)) { + return SessionStateNextStep.fromProto(message.unpackInto(nextStep)); + } + final success = pb.AuthenticationSuccess(); + if (message.canUnpackInto(success)) { + return SessionStateSuccess.fromProto(message.unpackInto(success)); + } + throw ArgumentError('Invalid SessionState: ${message.typeUrl}'); + } + + @override + Uint8List toSql(SessionState value) { + final proto = value.toProto(); + final any = pb.Any(); + pb.AnyMixin.packIntoAny(any, proto); + return any.writeToBuffer(); + } +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/converters/cedar_converters.dart b/services/celest_cloud_auth/lib/src/database/schema/converters/cedar_converters.dart new file mode 100644 index 00000000..48f25417 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/converters/cedar_converters.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; + +import 'package:cedar/cedar.dart'; +import 'package:drift/drift.dart' hide Value; + +final class CedarEntityConverter implements TypeConverter { + const CedarEntityConverter(); + + @override + Entity fromSql(String fromDb) { + return Entity.fromJson(jsonDecode(fromDb) as Map); + } + + @override + String toSql(Entity value) { + return jsonEncode(value.toJson()); + } +} + +final class CedarEntityUidConverter + implements TypeConverter { + const CedarEntityUidConverter(); + + @override + EntityUid fromSql(String fromDb) { + return EntityUid.fromJson(jsonDecode(fromDb) as Map); + } + + @override + String toSql(EntityUid value) { + return jsonEncode(value.toJson()); + } +} + +final class CedarAttributesConverter + implements TypeConverter, String> { + const CedarAttributesConverter(); + + @override + Map fromSql(String fromDb) { + return (jsonDecode(fromDb) as Map) + .cast() + .map((key, value) => MapEntry(key, Value.fromJson(value))); + } + + @override + String toSql(Map value) { + return jsonEncode(value.map((key, value) => MapEntry(key, value.toJson()))); + } +} + +final class CedarAuthorizationErrorsConverter + implements TypeConverter { + const CedarAuthorizationErrorsConverter(); + + @override + AuthorizationErrors fromSql(String fromDb) { + return AuthorizationErrors( + (jsonDecode(fromDb) as List) + .map( + (e) => AuthorizationException.fromJson(e as Map), + ) + .toList(), + ); + } + + @override + String toSql(AuthorizationErrors value) { + return jsonEncode( + value + .map( + // TODO(dnys1): Implement toJson for CedarAuthorizationError + (e) => { + 'policy_id': e.policyId, + 'message': e.message, + }, + ) + .toList(), + ); + } +} + +final class CedarAuthorizationReasonsConverter + implements TypeConverter, String> { + const CedarAuthorizationReasonsConverter(); + + @override + List fromSql(String fromDb) { + return (jsonDecode(fromDb) as List).cast(); + } + + @override + String toSql(List value) { + return jsonEncode(value); + } +} + +final class CedarEntityClosureConverter + implements TypeConverter, String> { + const CedarEntityClosureConverter(); + + @override + List fromSql(String fromDb) { + return (jsonDecode(fromDb) as List) + .map((e) => Entity.fromJson(e as Map)) + .toList(); + } + + @override + String toSql(List value) { + return jsonEncode(value.map((e) => e.toJson()).toList()); + } +} + +final class CedarPolicyConverter implements TypeConverter { + const CedarPolicyConverter(); + + @override + Policy fromSql(String fromDb) { + return Policy.fromJson(jsonDecode(fromDb) as Map); + } + + @override + String toSql(Policy value) { + return jsonEncode(value.toJson()); + } +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/converters/celest_converters.dart b/services/celest_cloud_auth/lib/src/database/schema/converters/celest_converters.dart new file mode 100644 index 00000000..84ebdd92 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/converters/celest_converters.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:celest_core/celest_core.dart'; +import 'package:drift/drift.dart'; + +export 'package:celest_core/celest_core.dart' show User, Email, PhoneNumber; + +final class EmailConverter implements TypeConverter { + const EmailConverter(); + + @override + Email fromSql(String fromDb) { + return Email.fromJson(jsonDecode(fromDb) as Map); + } + + @override + String toSql(Email value) { + return jsonEncode(value.toJson()); + } +} + +final class EmailsConverter implements TypeConverter, String> { + const EmailsConverter(); + + @override + List fromSql(String fromDb) { + final json = jsonDecode(fromDb) as List; + return json.map((e) => Email.fromJson(e as Map)).toList(); + } + + @override + String toSql(List value) { + return jsonEncode(value.map((e) => e.toJson()).toList()); + } +} + +final class PhoneNumberConverter implements TypeConverter { + const PhoneNumberConverter(); + + @override + PhoneNumber fromSql(String fromDb) { + return PhoneNumber.fromJson(jsonDecode(fromDb) as Map); + } + + @override + String toSql(PhoneNumber value) { + return jsonEncode(value.toJson()); + } +} + +final class PhoneNumbersConverter + implements TypeConverter, String> { + const PhoneNumbersConverter(); + + @override + List fromSql(String fromDb) { + final json = jsonDecode(fromDb) as List; + return json + .map((pn) => PhoneNumber.fromJson(pn as Map)) + .toList(); + } + + @override + String toSql(List value) { + return jsonEncode(value.map((pn) => pn.toJson()).toList()); + } +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/invocations.drift b/services/celest_cloud_auth/lib/src/database/schema/invocations.drift new file mode 100644 index 00000000..5f994015 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/invocations.drift @@ -0,0 +1,62 @@ +import 'projects.drift'; + +-- Stores Celest function invocation metadata. +CREATE TABLE IF NOT EXISTS celest_invocations ( + -- The unique identifier of the invocation. + invocation_id TEXT NOT NULL PRIMARY KEY, + + -- The ID of the function being invoked. + function_id TEXT NOT NULL, + + -- The time the invocation started. + start_time DATETIME NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + -- The time the invocation ended. + end_time DATETIME, + + -- The result of the invocation. + result BLOB, + + -- The error of the invocation. + error BLOB, + + CONSTRAINT celest_invocations_function_fk FOREIGN KEY (function_id) REFERENCES celest_functions(function_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Index for the invocation's FK. +CREATE INDEX IF NOT EXISTS celest_invocations_function_idx ON celest_invocations(function_id); + +-- Stores invocation log records. +CREATE TABLE IF NOT EXISTS celest_invocation_logs ( + -- The sequence number of the log. + sequence_id INTEGER NOT NULL, + + -- The ID of the invocation. + invocation_id TEXT NOT NULL, + + -- The time the log was created. + time DATETIME NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + -- The severity of the log. + severity TEXT NOT NULL, + + -- The message of the log. + message TEXT NOT NULL, + + -- The error associated with the log. + error TEXT, + + -- The stack trace associated with the log. + stack_trace TEXT, + + PRIMARY KEY (sequence_id, invocation_id), + + CONSTRAINT celest_invocation_logs_invocation_fk FOREIGN KEY (invocation_id) REFERENCES celest_invocations(invocation_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Index for the log's FK. +CREATE INDEX IF NOT EXISTS celest_invocation_logs_invocation_idx ON celest_invocation_logs(invocation_id); diff --git a/services/celest_cloud_auth/lib/src/database/schema/invocations.drift.dart b/services/celest_cloud_auth/lib/src/database/schema/invocations.drift.dart new file mode 100644 index 00000000..6755a3d5 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/invocations.drift.dart @@ -0,0 +1,1034 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_cloud_auth/src/database/schema/invocations.drift.dart' + as i1; +import 'dart:typed_data' as i2; + +class CelestInvocations extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CelestInvocations(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn invocationId = + i0.GeneratedColumn('invocation_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn functionId = i0.GeneratedColumn( + 'function_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn startTime = + i0.GeneratedColumn('start_time', aliasedName, false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn endTime = + i0.GeneratedColumn('end_time', aliasedName, true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn result = + i0.GeneratedColumn('result', aliasedName, true, + type: i0.DriftSqlType.blob, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn error = + i0.GeneratedColumn('error', aliasedName, true, + type: i0.DriftSqlType.blob, + requiredDuringInsert: false, + $customConstraints: ''); + @override + List get $columns => + [invocationId, functionId, startTime, endTime, result, error]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'celest_invocations'; + @override + Set get $primaryKey => {invocationId}; + @override + i1.CelestInvocation map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CelestInvocation( + invocationId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}invocation_id'])!, + functionId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}function_id'])!, + startTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, data['${effectivePrefix}start_time'])!, + endTime: attachedDatabase.typeMapping + .read(i0.DriftSqlType.dateTime, data['${effectivePrefix}end_time']), + result: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}result']), + error: attachedDatabase.typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}error']), + ); + } + + @override + CelestInvocations createAlias(String alias) { + return CelestInvocations(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'CONSTRAINT celest_invocations_function_fk FOREIGN KEY(function_id)REFERENCES celest_functions(function_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CelestInvocation extends i0.DataClass + implements i0.Insertable { + /// The unique identifier of the invocation. + final String invocationId; + + /// The ID of the function being invoked. + final String functionId; + + /// The time the invocation started. + final DateTime startTime; + + /// The time the invocation ended. + final DateTime? endTime; + + /// The result of the invocation. + final i2.Uint8List? result; + + /// The error of the invocation. + final i2.Uint8List? error; + const CelestInvocation( + {required this.invocationId, + required this.functionId, + required this.startTime, + this.endTime, + this.result, + this.error}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['invocation_id'] = i0.Variable(invocationId); + map['function_id'] = i0.Variable(functionId); + map['start_time'] = i0.Variable(startTime); + if (!nullToAbsent || endTime != null) { + map['end_time'] = i0.Variable(endTime); + } + if (!nullToAbsent || result != null) { + map['result'] = i0.Variable(result); + } + if (!nullToAbsent || error != null) { + map['error'] = i0.Variable(error); + } + return map; + } + + factory CelestInvocation.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CelestInvocation( + invocationId: serializer.fromJson(json['invocation_id']), + functionId: serializer.fromJson(json['function_id']), + startTime: serializer.fromJson(json['start_time']), + endTime: serializer.fromJson(json['end_time']), + result: serializer.fromJson(json['result']), + error: serializer.fromJson(json['error']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'invocation_id': serializer.toJson(invocationId), + 'function_id': serializer.toJson(functionId), + 'start_time': serializer.toJson(startTime), + 'end_time': serializer.toJson(endTime), + 'result': serializer.toJson(result), + 'error': serializer.toJson(error), + }; + } + + i1.CelestInvocation copyWith( + {String? invocationId, + String? functionId, + DateTime? startTime, + i0.Value endTime = const i0.Value.absent(), + i0.Value result = const i0.Value.absent(), + i0.Value error = const i0.Value.absent()}) => + i1.CelestInvocation( + invocationId: invocationId ?? this.invocationId, + functionId: functionId ?? this.functionId, + startTime: startTime ?? this.startTime, + endTime: endTime.present ? endTime.value : this.endTime, + result: result.present ? result.value : this.result, + error: error.present ? error.value : this.error, + ); + CelestInvocation copyWithCompanion(i1.CelestInvocationsCompanion data) { + return CelestInvocation( + invocationId: data.invocationId.present + ? data.invocationId.value + : this.invocationId, + functionId: + data.functionId.present ? data.functionId.value : this.functionId, + startTime: data.startTime.present ? data.startTime.value : this.startTime, + endTime: data.endTime.present ? data.endTime.value : this.endTime, + result: data.result.present ? data.result.value : this.result, + error: data.error.present ? data.error.value : this.error, + ); + } + + @override + String toString() { + return (StringBuffer('CelestInvocation(') + ..write('invocationId: $invocationId, ') + ..write('functionId: $functionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('result: $result, ') + ..write('error: $error') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(invocationId, functionId, startTime, endTime, + i0.$driftBlobEquality.hash(result), i0.$driftBlobEquality.hash(error)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CelestInvocation && + other.invocationId == this.invocationId && + other.functionId == this.functionId && + other.startTime == this.startTime && + other.endTime == this.endTime && + i0.$driftBlobEquality.equals(other.result, this.result) && + i0.$driftBlobEquality.equals(other.error, this.error)); +} + +class CelestInvocationsCompanion + extends i0.UpdateCompanion { + final i0.Value invocationId; + final i0.Value functionId; + final i0.Value startTime; + final i0.Value endTime; + final i0.Value result; + final i0.Value error; + final i0.Value rowid; + const CelestInvocationsCompanion({ + this.invocationId = const i0.Value.absent(), + this.functionId = const i0.Value.absent(), + this.startTime = const i0.Value.absent(), + this.endTime = const i0.Value.absent(), + this.result = const i0.Value.absent(), + this.error = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CelestInvocationsCompanion.insert({ + required String invocationId, + required String functionId, + this.startTime = const i0.Value.absent(), + this.endTime = const i0.Value.absent(), + this.result = const i0.Value.absent(), + this.error = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : invocationId = i0.Value(invocationId), + functionId = i0.Value(functionId); + static i0.Insertable custom({ + i0.Expression? invocationId, + i0.Expression? functionId, + i0.Expression? startTime, + i0.Expression? endTime, + i0.Expression? result, + i0.Expression? error, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (invocationId != null) 'invocation_id': invocationId, + if (functionId != null) 'function_id': functionId, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (result != null) 'result': result, + if (error != null) 'error': error, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CelestInvocationsCompanion copyWith( + {i0.Value? invocationId, + i0.Value? functionId, + i0.Value? startTime, + i0.Value? endTime, + i0.Value? result, + i0.Value? error, + i0.Value? rowid}) { + return i1.CelestInvocationsCompanion( + invocationId: invocationId ?? this.invocationId, + functionId: functionId ?? this.functionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + result: result ?? this.result, + error: error ?? this.error, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (invocationId.present) { + map['invocation_id'] = i0.Variable(invocationId.value); + } + if (functionId.present) { + map['function_id'] = i0.Variable(functionId.value); + } + if (startTime.present) { + map['start_time'] = i0.Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = i0.Variable(endTime.value); + } + if (result.present) { + map['result'] = i0.Variable(result.value); + } + if (error.present) { + map['error'] = i0.Variable(error.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CelestInvocationsCompanion(') + ..write('invocationId: $invocationId, ') + ..write('functionId: $functionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('result: $result, ') + ..write('error: $error, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CelestInvocationsCreateCompanionBuilder = i1.CelestInvocationsCompanion + Function({ + required String invocationId, + required String functionId, + i0.Value startTime, + i0.Value endTime, + i0.Value result, + i0.Value error, + i0.Value rowid, +}); +typedef $CelestInvocationsUpdateCompanionBuilder = i1.CelestInvocationsCompanion + Function({ + i0.Value invocationId, + i0.Value functionId, + i0.Value startTime, + i0.Value endTime, + i0.Value result, + i0.Value error, + i0.Value rowid, +}); + +class $CelestInvocationsFilterComposer + extends i0.FilterComposer { + $CelestInvocationsFilterComposer(super.$state); + i0.ColumnFilters get invocationId => $state.composableBuilder( + column: $state.table.invocationId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get functionId => $state.composableBuilder( + column: $state.table.functionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get startTime => $state.composableBuilder( + column: $state.table.startTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get endTime => $state.composableBuilder( + column: $state.table.endTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get result => $state.composableBuilder( + column: $state.table.result, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get error => $state.composableBuilder( + column: $state.table.error, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CelestInvocationsOrderingComposer + extends i0.OrderingComposer { + $CelestInvocationsOrderingComposer(super.$state); + i0.ColumnOrderings get invocationId => $state.composableBuilder( + column: $state.table.invocationId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get functionId => $state.composableBuilder( + column: $state.table.functionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get startTime => $state.composableBuilder( + column: $state.table.startTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get endTime => $state.composableBuilder( + column: $state.table.endTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get result => $state.composableBuilder( + column: $state.table.result, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get error => $state.composableBuilder( + column: $state.table.error, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CelestInvocationsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CelestInvocations, + i1.CelestInvocation, + i1.$CelestInvocationsFilterComposer, + i1.$CelestInvocationsOrderingComposer, + $CelestInvocationsCreateCompanionBuilder, + $CelestInvocationsUpdateCompanionBuilder, + ( + i1.CelestInvocation, + i0.BaseReferences + ), + i1.CelestInvocation, + i0.PrefetchHooks Function()> { + $CelestInvocationsTableManager( + i0.GeneratedDatabase db, i1.CelestInvocations table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CelestInvocationsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: i1 + .$CelestInvocationsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value invocationId = const i0.Value.absent(), + i0.Value functionId = const i0.Value.absent(), + i0.Value startTime = const i0.Value.absent(), + i0.Value endTime = const i0.Value.absent(), + i0.Value result = const i0.Value.absent(), + i0.Value error = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestInvocationsCompanion( + invocationId: invocationId, + functionId: functionId, + startTime: startTime, + endTime: endTime, + result: result, + error: error, + rowid: rowid, + ), + createCompanionCallback: ({ + required String invocationId, + required String functionId, + i0.Value startTime = const i0.Value.absent(), + i0.Value endTime = const i0.Value.absent(), + i0.Value result = const i0.Value.absent(), + i0.Value error = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestInvocationsCompanion.insert( + invocationId: invocationId, + functionId: functionId, + startTime: startTime, + endTime: endTime, + result: result, + error: error, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CelestInvocationsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CelestInvocations, + i1.CelestInvocation, + i1.$CelestInvocationsFilterComposer, + i1.$CelestInvocationsOrderingComposer, + $CelestInvocationsCreateCompanionBuilder, + $CelestInvocationsUpdateCompanionBuilder, + ( + i1.CelestInvocation, + i0.BaseReferences + ), + i1.CelestInvocation, + i0.PrefetchHooks Function()>; +i0.Index get celestInvocationsFunctionIdx => i0.Index( + 'celest_invocations_function_idx', + 'CREATE INDEX IF NOT EXISTS celest_invocations_function_idx ON celest_invocations (function_id)'); + +class CelestInvocationLogs extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CelestInvocationLogs(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn sequenceId = i0.GeneratedColumn( + 'sequence_id', aliasedName, false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn invocationId = + i0.GeneratedColumn('invocation_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn time = i0.GeneratedColumn( + 'time', aliasedName, false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn severity = i0.GeneratedColumn( + 'severity', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn message = i0.GeneratedColumn( + 'message', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn error = i0.GeneratedColumn( + 'error', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn stackTrace = i0.GeneratedColumn( + 'stack_trace', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + @override + List get $columns => + [sequenceId, invocationId, time, severity, message, error, stackTrace]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'celest_invocation_logs'; + @override + Set get $primaryKey => {sequenceId, invocationId}; + @override + i1.CelestInvocationLog map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CelestInvocationLog( + sequenceId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}sequence_id'])!, + invocationId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}invocation_id'])!, + time: attachedDatabase.typeMapping + .read(i0.DriftSqlType.dateTime, data['${effectivePrefix}time'])!, + severity: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}severity'])!, + message: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}message'])!, + error: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}error']), + stackTrace: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}stack_trace']), + ); + } + + @override + CelestInvocationLogs createAlias(String alias) { + return CelestInvocationLogs(attachedDatabase, alias); + } + + @override + List get customConstraints => const [ + 'PRIMARY KEY(sequence_id, invocation_id)', + 'CONSTRAINT celest_invocation_logs_invocation_fk FOREIGN KEY(invocation_id)REFERENCES celest_invocations(invocation_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CelestInvocationLog extends i0.DataClass + implements i0.Insertable { + /// The sequence number of the log. + final int sequenceId; + + /// The ID of the invocation. + final String invocationId; + + /// The time the log was created. + final DateTime time; + + /// The severity of the log. + final String severity; + + /// The message of the log. + final String message; + + /// The error associated with the log. + final String? error; + + /// The stack trace associated with the log. + final String? stackTrace; + const CelestInvocationLog( + {required this.sequenceId, + required this.invocationId, + required this.time, + required this.severity, + required this.message, + this.error, + this.stackTrace}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['sequence_id'] = i0.Variable(sequenceId); + map['invocation_id'] = i0.Variable(invocationId); + map['time'] = i0.Variable(time); + map['severity'] = i0.Variable(severity); + map['message'] = i0.Variable(message); + if (!nullToAbsent || error != null) { + map['error'] = i0.Variable(error); + } + if (!nullToAbsent || stackTrace != null) { + map['stack_trace'] = i0.Variable(stackTrace); + } + return map; + } + + factory CelestInvocationLog.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CelestInvocationLog( + sequenceId: serializer.fromJson(json['sequence_id']), + invocationId: serializer.fromJson(json['invocation_id']), + time: serializer.fromJson(json['time']), + severity: serializer.fromJson(json['severity']), + message: serializer.fromJson(json['message']), + error: serializer.fromJson(json['error']), + stackTrace: serializer.fromJson(json['stack_trace']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'sequence_id': serializer.toJson(sequenceId), + 'invocation_id': serializer.toJson(invocationId), + 'time': serializer.toJson(time), + 'severity': serializer.toJson(severity), + 'message': serializer.toJson(message), + 'error': serializer.toJson(error), + 'stack_trace': serializer.toJson(stackTrace), + }; + } + + i1.CelestInvocationLog copyWith( + {int? sequenceId, + String? invocationId, + DateTime? time, + String? severity, + String? message, + i0.Value error = const i0.Value.absent(), + i0.Value stackTrace = const i0.Value.absent()}) => + i1.CelestInvocationLog( + sequenceId: sequenceId ?? this.sequenceId, + invocationId: invocationId ?? this.invocationId, + time: time ?? this.time, + severity: severity ?? this.severity, + message: message ?? this.message, + error: error.present ? error.value : this.error, + stackTrace: stackTrace.present ? stackTrace.value : this.stackTrace, + ); + CelestInvocationLog copyWithCompanion(i1.CelestInvocationLogsCompanion data) { + return CelestInvocationLog( + sequenceId: + data.sequenceId.present ? data.sequenceId.value : this.sequenceId, + invocationId: data.invocationId.present + ? data.invocationId.value + : this.invocationId, + time: data.time.present ? data.time.value : this.time, + severity: data.severity.present ? data.severity.value : this.severity, + message: data.message.present ? data.message.value : this.message, + error: data.error.present ? data.error.value : this.error, + stackTrace: + data.stackTrace.present ? data.stackTrace.value : this.stackTrace, + ); + } + + @override + String toString() { + return (StringBuffer('CelestInvocationLog(') + ..write('sequenceId: $sequenceId, ') + ..write('invocationId: $invocationId, ') + ..write('time: $time, ') + ..write('severity: $severity, ') + ..write('message: $message, ') + ..write('error: $error, ') + ..write('stackTrace: $stackTrace') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + sequenceId, invocationId, time, severity, message, error, stackTrace); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CelestInvocationLog && + other.sequenceId == this.sequenceId && + other.invocationId == this.invocationId && + other.time == this.time && + other.severity == this.severity && + other.message == this.message && + other.error == this.error && + other.stackTrace == this.stackTrace); +} + +class CelestInvocationLogsCompanion + extends i0.UpdateCompanion { + final i0.Value sequenceId; + final i0.Value invocationId; + final i0.Value time; + final i0.Value severity; + final i0.Value message; + final i0.Value error; + final i0.Value stackTrace; + final i0.Value rowid; + const CelestInvocationLogsCompanion({ + this.sequenceId = const i0.Value.absent(), + this.invocationId = const i0.Value.absent(), + this.time = const i0.Value.absent(), + this.severity = const i0.Value.absent(), + this.message = const i0.Value.absent(), + this.error = const i0.Value.absent(), + this.stackTrace = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CelestInvocationLogsCompanion.insert({ + required int sequenceId, + required String invocationId, + this.time = const i0.Value.absent(), + required String severity, + required String message, + this.error = const i0.Value.absent(), + this.stackTrace = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : sequenceId = i0.Value(sequenceId), + invocationId = i0.Value(invocationId), + severity = i0.Value(severity), + message = i0.Value(message); + static i0.Insertable custom({ + i0.Expression? sequenceId, + i0.Expression? invocationId, + i0.Expression? time, + i0.Expression? severity, + i0.Expression? message, + i0.Expression? error, + i0.Expression? stackTrace, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (sequenceId != null) 'sequence_id': sequenceId, + if (invocationId != null) 'invocation_id': invocationId, + if (time != null) 'time': time, + if (severity != null) 'severity': severity, + if (message != null) 'message': message, + if (error != null) 'error': error, + if (stackTrace != null) 'stack_trace': stackTrace, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CelestInvocationLogsCompanion copyWith( + {i0.Value? sequenceId, + i0.Value? invocationId, + i0.Value? time, + i0.Value? severity, + i0.Value? message, + i0.Value? error, + i0.Value? stackTrace, + i0.Value? rowid}) { + return i1.CelestInvocationLogsCompanion( + sequenceId: sequenceId ?? this.sequenceId, + invocationId: invocationId ?? this.invocationId, + time: time ?? this.time, + severity: severity ?? this.severity, + message: message ?? this.message, + error: error ?? this.error, + stackTrace: stackTrace ?? this.stackTrace, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sequenceId.present) { + map['sequence_id'] = i0.Variable(sequenceId.value); + } + if (invocationId.present) { + map['invocation_id'] = i0.Variable(invocationId.value); + } + if (time.present) { + map['time'] = i0.Variable(time.value); + } + if (severity.present) { + map['severity'] = i0.Variable(severity.value); + } + if (message.present) { + map['message'] = i0.Variable(message.value); + } + if (error.present) { + map['error'] = i0.Variable(error.value); + } + if (stackTrace.present) { + map['stack_trace'] = i0.Variable(stackTrace.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CelestInvocationLogsCompanion(') + ..write('sequenceId: $sequenceId, ') + ..write('invocationId: $invocationId, ') + ..write('time: $time, ') + ..write('severity: $severity, ') + ..write('message: $message, ') + ..write('error: $error, ') + ..write('stackTrace: $stackTrace, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CelestInvocationLogsCreateCompanionBuilder + = i1.CelestInvocationLogsCompanion Function({ + required int sequenceId, + required String invocationId, + i0.Value time, + required String severity, + required String message, + i0.Value error, + i0.Value stackTrace, + i0.Value rowid, +}); +typedef $CelestInvocationLogsUpdateCompanionBuilder + = i1.CelestInvocationLogsCompanion Function({ + i0.Value sequenceId, + i0.Value invocationId, + i0.Value time, + i0.Value severity, + i0.Value message, + i0.Value error, + i0.Value stackTrace, + i0.Value rowid, +}); + +class $CelestInvocationLogsFilterComposer + extends i0.FilterComposer { + $CelestInvocationLogsFilterComposer(super.$state); + i0.ColumnFilters get sequenceId => $state.composableBuilder( + column: $state.table.sequenceId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get invocationId => $state.composableBuilder( + column: $state.table.invocationId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get time => $state.composableBuilder( + column: $state.table.time, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get severity => $state.composableBuilder( + column: $state.table.severity, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get message => $state.composableBuilder( + column: $state.table.message, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get error => $state.composableBuilder( + column: $state.table.error, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get stackTrace => $state.composableBuilder( + column: $state.table.stackTrace, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CelestInvocationLogsOrderingComposer + extends i0.OrderingComposer { + $CelestInvocationLogsOrderingComposer(super.$state); + i0.ColumnOrderings get sequenceId => $state.composableBuilder( + column: $state.table.sequenceId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get invocationId => $state.composableBuilder( + column: $state.table.invocationId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get time => $state.composableBuilder( + column: $state.table.time, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get severity => $state.composableBuilder( + column: $state.table.severity, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get message => $state.composableBuilder( + column: $state.table.message, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get error => $state.composableBuilder( + column: $state.table.error, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get stackTrace => $state.composableBuilder( + column: $state.table.stackTrace, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CelestInvocationLogsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CelestInvocationLogs, + i1.CelestInvocationLog, + i1.$CelestInvocationLogsFilterComposer, + i1.$CelestInvocationLogsOrderingComposer, + $CelestInvocationLogsCreateCompanionBuilder, + $CelestInvocationLogsUpdateCompanionBuilder, + ( + i1.CelestInvocationLog, + i0.BaseReferences + ), + i1.CelestInvocationLog, + i0.PrefetchHooks Function()> { + $CelestInvocationLogsTableManager( + i0.GeneratedDatabase db, i1.CelestInvocationLogs table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: i1 + .$CelestInvocationLogsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: i1.$CelestInvocationLogsOrderingComposer( + i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value sequenceId = const i0.Value.absent(), + i0.Value invocationId = const i0.Value.absent(), + i0.Value time = const i0.Value.absent(), + i0.Value severity = const i0.Value.absent(), + i0.Value message = const i0.Value.absent(), + i0.Value error = const i0.Value.absent(), + i0.Value stackTrace = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestInvocationLogsCompanion( + sequenceId: sequenceId, + invocationId: invocationId, + time: time, + severity: severity, + message: message, + error: error, + stackTrace: stackTrace, + rowid: rowid, + ), + createCompanionCallback: ({ + required int sequenceId, + required String invocationId, + i0.Value time = const i0.Value.absent(), + required String severity, + required String message, + i0.Value error = const i0.Value.absent(), + i0.Value stackTrace = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestInvocationLogsCompanion.insert( + sequenceId: sequenceId, + invocationId: invocationId, + time: time, + severity: severity, + message: message, + error: error, + stackTrace: stackTrace, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CelestInvocationLogsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CelestInvocationLogs, + i1.CelestInvocationLog, + i1.$CelestInvocationLogsFilterComposer, + i1.$CelestInvocationLogsOrderingComposer, + $CelestInvocationLogsCreateCompanionBuilder, + $CelestInvocationLogsUpdateCompanionBuilder, + ( + i1.CelestInvocationLog, + i0.BaseReferences + ), + i1.CelestInvocationLog, + i0.PrefetchHooks Function()>; +i0.Index get celestInvocationLogsInvocationIdx => i0.Index( + 'celest_invocation_logs_invocation_idx', + 'CREATE INDEX IF NOT EXISTS celest_invocation_logs_invocation_idx ON celest_invocation_logs (invocation_id)'); diff --git a/services/celest_cloud_auth/lib/src/database/schema/projects.drift b/services/celest_cloud_auth/lib/src/database/schema/projects.drift new file mode 100644 index 00000000..3bbc66df --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/projects.drift @@ -0,0 +1,188 @@ +import 'cedar.drift'; + +import 'schema_imports.dart'; + +-- Stores Celest project metadata. +CREATE TABLE IF NOT EXISTS celest_projects ( + -- The unique identifier of the project. + project_id TEXT NOT NULL PRIMARY KEY, + + -- The version of the project. + version TEXT NOT NULL, + + -- The resolved AST. + -- + -- Format: Proto[celest.ast.v1.ResolvedProject] + resolved_ast BLOB NOT NULL + MAPPED BY `const ResolvedProjectConverter()`, + + -- A hash of the project metadata. + etag TEXT NOT NULL +); + +-- Stores Celest API metadata. +CREATE TABLE IF NOT EXISTS celest_apis ( + -- The unique identifier of the API. + api_id TEXT NOT NULL PRIMARY KEY, + + -- The project ID of the API. + project_id TEXT NOT NULL, + + -- The resolved AST. + -- + -- Format: Proto[celest.ast.v1.ResolvedApi] + resolved_ast BLOB NOT NULL + MAPPED BY `const ResolvedApiConverter()`, + + -- A hash of the API metadata. + etag TEXT NOT NULL, + + CONSTRAINT celest_apis_project_fk FOREIGN KEY (project_id) REFERENCES celest_projects(project_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Indexes for the API's FKs. +CREATE INDEX IF NOT EXISTS celest_apis_project_idx ON celest_apis(project_id); + +---------------------------------------------- +-------------- Cedar Triggers ---------------- +---------------------------------------------- + +-- This trigger is used to create the Cedar entity when a row is inserted. +CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_create +BEFORE INSERT ON celest_apis +BEGIN + INSERT INTO cedar_entities(entity_type, entity_id) + VALUES ('Celest::Api', NEW.api_id); +END; + +-- This trigger is used to delete the entity and relationships when a row is deleted. +CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_delete +AFTER DELETE ON celest_apis +BEGIN + DELETE FROM cedar_relationships + WHERE + entity_type = 'Celest::Api' + AND entity_id = OLD.api_id; + DELETE FROM cedar_relationships + WHERE + parent_type = 'Celest::Api' + AND parent_id = OLD.api_id; + DELETE FROM cedar_entities + WHERE + entity_type = 'Celest::Api' + AND entity_id = OLD.api_id; +END; + +---------------------------------------------- +-------------- /Cedar Triggers --------------- +---------------------------------------------- + +-- Stores Celest function metadata. +CREATE TABLE IF NOT EXISTS celest_functions ( + -- The unique identifier of the function. + function_id TEXT NOT NULL PRIMARY KEY, + + -- The API ID of the function. + api_id TEXT NOT NULL, + + -- The resolved function AST. + -- + -- Format: Proto[celest.ast.v1.ResolvedFunction] + resolved_ast BLOB NOT NULL + MAPPED BY `const ResolvedFunctionConverter()`, + + -- A hash of the function metadata. + etag TEXT NOT NULL, + + CONSTRAINT celest_functions_api_fk FOREIGN KEY (api_id) REFERENCES celest_apis(api_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); + +-- Indexes for the function's FKs. +CREATE INDEX IF NOT EXISTS celest_functions_api_idx ON celest_functions(api_id); + +---------------------------------------------- +-------------- Cedar Triggers ---------------- +---------------------------------------------- + +-- This trigger is used to create the Cedar entity when a row is inserted. +CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_create +BEFORE INSERT ON celest_functions +BEGIN + INSERT INTO cedar_entities(entity_type, entity_id) + VALUES ('Celest::Function', NEW.function_id); + INSERT INTO cedar_relationships(entity_type, entity_id, parent_type, parent_id) + VALUES ('Celest::Function', NEW.function_id, 'Celest::Api', NEW.api_id); +END; + +-- This trigger is used to delete the entity and relationships when a row is deleted. +CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_delete +AFTER DELETE ON celest_functions +BEGIN + DELETE FROM cedar_relationships + WHERE + entity_type = 'Celest::Function' + AND entity_id = OLD.function_id; + DELETE FROM cedar_relationships + WHERE + parent_type = 'Celest::Function' + AND parent_id = OLD.function_id; + DELETE FROM cedar_entities + WHERE + entity_type = 'Celest::Function' + AND entity_id = OLD.function_id; +END; + +---------------------------------------------- +-------------- /Cedar Triggers --------------- +---------------------------------------------- + +upsertProject: + INSERT INTO celest_projects (project_id, version, resolved_ast, etag) + VALUES (:project_id, :version, :resolved_ast, :etag) + ON CONFLICT(project_id) DO UPDATE SET + version = excluded.version, + resolved_ast = excluded.resolved_ast, + etag = excluded.etag + RETURNING *; + +getProject: + SELECT * FROM celest_projects + WHERE project_id = :project_id; + +upsertApi: + INSERT INTO celest_apis (api_id, project_id, resolved_ast, etag) + VALUES (:api_id, :project_id, :resolved_ast, :etag) + ON CONFLICT(api_id) DO UPDATE SET + project_id = excluded.project_id, + resolved_ast = excluded.resolved_ast, + etag = excluded.etag + RETURNING *; + +getApi: + SELECT * FROM celest_apis + WHERE api_id = :api_id; + +listApis: + SELECT * FROM celest_apis + WHERE project_id = :project_id; + +upsertFunction: + INSERT INTO celest_functions (function_id, api_id, resolved_ast, etag) + VALUES (:function_id, :api_id, :resolved_ast, :etag) + ON CONFLICT(function_id) DO UPDATE SET + api_id = excluded.api_id, + resolved_ast = excluded.resolved_ast, + etag = excluded.etag + RETURNING *; + +getFunction: + SELECT * FROM celest_functions + WHERE function_id = :function_id; + +listFunctions: + SELECT * FROM celest_functions + WHERE api_id = :api_id; diff --git a/services/celest_cloud_auth/lib/src/database/schema/projects.drift.dart b/services/celest_cloud_auth/lib/src/database/schema/projects.drift.dart new file mode 100644 index 00000000..d03f127d --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/projects.drift.dart @@ -0,0 +1,1338 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_cloud_auth/src/database/schema/projects.drift.dart' + as i1; +import 'package:celest_ast/src/resolved_ast.dart' as i2; +import 'dart:typed_data' as i3; +import 'package:celest_cloud_auth/src/database/schema/converters/ast_converters.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; +import 'dart:async' as i6; + +class CelestProjects extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CelestProjects(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn projectId = i0.GeneratedColumn( + 'project_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn version = i0.GeneratedColumn( + 'version', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0 + .GeneratedColumnWithTypeConverter + resolvedAst = i0.GeneratedColumn( + 'resolved_ast', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter( + i1.CelestProjects.$converterresolvedAst); + late final i0.GeneratedColumn etag = i0.GeneratedColumn( + 'etag', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => + [projectId, version, resolvedAst, etag]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'celest_projects'; + @override + Set get $primaryKey => {projectId}; + @override + i1.CelestProject map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CelestProject( + projectId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}project_id'])!, + version: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}version'])!, + resolvedAst: i1.CelestProjects.$converterresolvedAst.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, data['${effectivePrefix}resolved_ast'])!), + etag: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}etag'])!, + ); + } + + @override + CelestProjects createAlias(String alias) { + return CelestProjects(attachedDatabase, alias); + } + + static i0.TypeConverter + $converterresolvedAst = const i4.ResolvedProjectConverter(); + @override + bool get dontWriteConstraints => true; +} + +class CelestProject extends i0.DataClass + implements i0.Insertable { + /// The unique identifier of the project. + final String projectId; + + /// The version of the project. + final String version; + + /// Format: Proto[celest.ast.v1.ResolvedProject] + /// + /// The resolved AST. + final i2.ResolvedProject resolvedAst; + + /// A hash of the project metadata. + final String etag; + const CelestProject( + {required this.projectId, + required this.version, + required this.resolvedAst, + required this.etag}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['project_id'] = i0.Variable(projectId); + map['version'] = i0.Variable(version); + { + map['resolved_ast'] = i0.Variable( + i1.CelestProjects.$converterresolvedAst.toSql(resolvedAst)); + } + map['etag'] = i0.Variable(etag); + return map; + } + + factory CelestProject.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CelestProject( + projectId: serializer.fromJson(json['project_id']), + version: serializer.fromJson(json['version']), + resolvedAst: + serializer.fromJson(json['resolved_ast']), + etag: serializer.fromJson(json['etag']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'project_id': serializer.toJson(projectId), + 'version': serializer.toJson(version), + 'resolved_ast': serializer.toJson(resolvedAst), + 'etag': serializer.toJson(etag), + }; + } + + i1.CelestProject copyWith( + {String? projectId, + String? version, + i2.ResolvedProject? resolvedAst, + String? etag}) => + i1.CelestProject( + projectId: projectId ?? this.projectId, + version: version ?? this.version, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + ); + CelestProject copyWithCompanion(i1.CelestProjectsCompanion data) { + return CelestProject( + projectId: data.projectId.present ? data.projectId.value : this.projectId, + version: data.version.present ? data.version.value : this.version, + resolvedAst: + data.resolvedAst.present ? data.resolvedAst.value : this.resolvedAst, + etag: data.etag.present ? data.etag.value : this.etag, + ); + } + + @override + String toString() { + return (StringBuffer('CelestProject(') + ..write('projectId: $projectId, ') + ..write('version: $version, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(projectId, version, resolvedAst, etag); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CelestProject && + other.projectId == this.projectId && + other.version == this.version && + other.resolvedAst == this.resolvedAst && + other.etag == this.etag); +} + +class CelestProjectsCompanion extends i0.UpdateCompanion { + final i0.Value projectId; + final i0.Value version; + final i0.Value resolvedAst; + final i0.Value etag; + final i0.Value rowid; + const CelestProjectsCompanion({ + this.projectId = const i0.Value.absent(), + this.version = const i0.Value.absent(), + this.resolvedAst = const i0.Value.absent(), + this.etag = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CelestProjectsCompanion.insert({ + required String projectId, + required String version, + required i2.ResolvedProject resolvedAst, + required String etag, + this.rowid = const i0.Value.absent(), + }) : projectId = i0.Value(projectId), + version = i0.Value(version), + resolvedAst = i0.Value(resolvedAst), + etag = i0.Value(etag); + static i0.Insertable custom({ + i0.Expression? projectId, + i0.Expression? version, + i0.Expression? resolvedAst, + i0.Expression? etag, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (projectId != null) 'project_id': projectId, + if (version != null) 'version': version, + if (resolvedAst != null) 'resolved_ast': resolvedAst, + if (etag != null) 'etag': etag, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CelestProjectsCompanion copyWith( + {i0.Value? projectId, + i0.Value? version, + i0.Value? resolvedAst, + i0.Value? etag, + i0.Value? rowid}) { + return i1.CelestProjectsCompanion( + projectId: projectId ?? this.projectId, + version: version ?? this.version, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (projectId.present) { + map['project_id'] = i0.Variable(projectId.value); + } + if (version.present) { + map['version'] = i0.Variable(version.value); + } + if (resolvedAst.present) { + map['resolved_ast'] = i0.Variable( + i1.CelestProjects.$converterresolvedAst.toSql(resolvedAst.value)); + } + if (etag.present) { + map['etag'] = i0.Variable(etag.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CelestProjectsCompanion(') + ..write('projectId: $projectId, ') + ..write('version: $version, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CelestProjectsCreateCompanionBuilder = i1.CelestProjectsCompanion + Function({ + required String projectId, + required String version, + required i2.ResolvedProject resolvedAst, + required String etag, + i0.Value rowid, +}); +typedef $CelestProjectsUpdateCompanionBuilder = i1.CelestProjectsCompanion + Function({ + i0.Value projectId, + i0.Value version, + i0.Value resolvedAst, + i0.Value etag, + i0.Value rowid, +}); + +class $CelestProjectsFilterComposer + extends i0.FilterComposer { + $CelestProjectsFilterComposer(super.$state); + i0.ColumnFilters get projectId => $state.composableBuilder( + column: $state.table.projectId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get version => $state.composableBuilder( + column: $state.table.version, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CelestProjectsOrderingComposer + extends i0.OrderingComposer { + $CelestProjectsOrderingComposer(super.$state); + i0.ColumnOrderings get projectId => $state.composableBuilder( + column: $state.table.projectId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get version => $state.composableBuilder( + column: $state.table.version, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CelestProjectsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CelestProjects, + i1.CelestProject, + i1.$CelestProjectsFilterComposer, + i1.$CelestProjectsOrderingComposer, + $CelestProjectsCreateCompanionBuilder, + $CelestProjectsUpdateCompanionBuilder, + ( + i1.CelestProject, + i0 + .BaseReferences + ), + i1.CelestProject, + i0.PrefetchHooks Function()> { + $CelestProjectsTableManager(i0.GeneratedDatabase db, i1.CelestProjects table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CelestProjectsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CelestProjectsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value projectId = const i0.Value.absent(), + i0.Value version = const i0.Value.absent(), + i0.Value resolvedAst = const i0.Value.absent(), + i0.Value etag = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestProjectsCompanion( + projectId: projectId, + version: version, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + createCompanionCallback: ({ + required String projectId, + required String version, + required i2.ResolvedProject resolvedAst, + required String etag, + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestProjectsCompanion.insert( + projectId: projectId, + version: version, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CelestProjectsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CelestProjects, + i1.CelestProject, + i1.$CelestProjectsFilterComposer, + i1.$CelestProjectsOrderingComposer, + $CelestProjectsCreateCompanionBuilder, + $CelestProjectsUpdateCompanionBuilder, + ( + i1.CelestProject, + i0 + .BaseReferences + ), + i1.CelestProject, + i0.PrefetchHooks Function()>; + +class CelestApis extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CelestApis(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn apiId = i0.GeneratedColumn( + 'api_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn projectId = i0.GeneratedColumn( + 'project_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumnWithTypeConverter + resolvedAst = i0.GeneratedColumn( + 'resolved_ast', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(i1.CelestApis.$converterresolvedAst); + late final i0.GeneratedColumn etag = i0.GeneratedColumn( + 'etag', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => + [apiId, projectId, resolvedAst, etag]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'celest_apis'; + @override + Set get $primaryKey => {apiId}; + @override + i1.CelestApi map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CelestApi( + apiId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}api_id'])!, + projectId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}project_id'])!, + resolvedAst: i1.CelestApis.$converterresolvedAst.fromSql(attachedDatabase + .typeMapping + .read(i0.DriftSqlType.blob, data['${effectivePrefix}resolved_ast'])!), + etag: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}etag'])!, + ); + } + + @override + CelestApis createAlias(String alias) { + return CelestApis(attachedDatabase, alias); + } + + static i0.TypeConverter $converterresolvedAst = + const i4.ResolvedApiConverter(); + @override + List get customConstraints => const [ + 'CONSTRAINT celest_apis_project_fk FOREIGN KEY(project_id)REFERENCES celest_projects(project_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CelestApi extends i0.DataClass implements i0.Insertable { + /// The unique identifier of the API. + final String apiId; + + /// The project ID of the API. + final String projectId; + + /// Format: Proto[celest.ast.v1.ResolvedApi] + /// + /// The resolved AST. + final i2.ResolvedApi resolvedAst; + + /// A hash of the API metadata. + final String etag; + const CelestApi( + {required this.apiId, + required this.projectId, + required this.resolvedAst, + required this.etag}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['api_id'] = i0.Variable(apiId); + map['project_id'] = i0.Variable(projectId); + { + map['resolved_ast'] = i0.Variable( + i1.CelestApis.$converterresolvedAst.toSql(resolvedAst)); + } + map['etag'] = i0.Variable(etag); + return map; + } + + factory CelestApi.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CelestApi( + apiId: serializer.fromJson(json['api_id']), + projectId: serializer.fromJson(json['project_id']), + resolvedAst: serializer.fromJson(json['resolved_ast']), + etag: serializer.fromJson(json['etag']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'api_id': serializer.toJson(apiId), + 'project_id': serializer.toJson(projectId), + 'resolved_ast': serializer.toJson(resolvedAst), + 'etag': serializer.toJson(etag), + }; + } + + i1.CelestApi copyWith( + {String? apiId, + String? projectId, + i2.ResolvedApi? resolvedAst, + String? etag}) => + i1.CelestApi( + apiId: apiId ?? this.apiId, + projectId: projectId ?? this.projectId, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + ); + CelestApi copyWithCompanion(i1.CelestApisCompanion data) { + return CelestApi( + apiId: data.apiId.present ? data.apiId.value : this.apiId, + projectId: data.projectId.present ? data.projectId.value : this.projectId, + resolvedAst: + data.resolvedAst.present ? data.resolvedAst.value : this.resolvedAst, + etag: data.etag.present ? data.etag.value : this.etag, + ); + } + + @override + String toString() { + return (StringBuffer('CelestApi(') + ..write('apiId: $apiId, ') + ..write('projectId: $projectId, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(apiId, projectId, resolvedAst, etag); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CelestApi && + other.apiId == this.apiId && + other.projectId == this.projectId && + other.resolvedAst == this.resolvedAst && + other.etag == this.etag); +} + +class CelestApisCompanion extends i0.UpdateCompanion { + final i0.Value apiId; + final i0.Value projectId; + final i0.Value resolvedAst; + final i0.Value etag; + final i0.Value rowid; + const CelestApisCompanion({ + this.apiId = const i0.Value.absent(), + this.projectId = const i0.Value.absent(), + this.resolvedAst = const i0.Value.absent(), + this.etag = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CelestApisCompanion.insert({ + required String apiId, + required String projectId, + required i2.ResolvedApi resolvedAst, + required String etag, + this.rowid = const i0.Value.absent(), + }) : apiId = i0.Value(apiId), + projectId = i0.Value(projectId), + resolvedAst = i0.Value(resolvedAst), + etag = i0.Value(etag); + static i0.Insertable custom({ + i0.Expression? apiId, + i0.Expression? projectId, + i0.Expression? resolvedAst, + i0.Expression? etag, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (apiId != null) 'api_id': apiId, + if (projectId != null) 'project_id': projectId, + if (resolvedAst != null) 'resolved_ast': resolvedAst, + if (etag != null) 'etag': etag, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CelestApisCompanion copyWith( + {i0.Value? apiId, + i0.Value? projectId, + i0.Value? resolvedAst, + i0.Value? etag, + i0.Value? rowid}) { + return i1.CelestApisCompanion( + apiId: apiId ?? this.apiId, + projectId: projectId ?? this.projectId, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (apiId.present) { + map['api_id'] = i0.Variable(apiId.value); + } + if (projectId.present) { + map['project_id'] = i0.Variable(projectId.value); + } + if (resolvedAst.present) { + map['resolved_ast'] = i0.Variable( + i1.CelestApis.$converterresolvedAst.toSql(resolvedAst.value)); + } + if (etag.present) { + map['etag'] = i0.Variable(etag.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CelestApisCompanion(') + ..write('apiId: $apiId, ') + ..write('projectId: $projectId, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CelestApisCreateCompanionBuilder = i1.CelestApisCompanion Function({ + required String apiId, + required String projectId, + required i2.ResolvedApi resolvedAst, + required String etag, + i0.Value rowid, +}); +typedef $CelestApisUpdateCompanionBuilder = i1.CelestApisCompanion Function({ + i0.Value apiId, + i0.Value projectId, + i0.Value resolvedAst, + i0.Value etag, + i0.Value rowid, +}); + +class $CelestApisFilterComposer + extends i0.FilterComposer { + $CelestApisFilterComposer(super.$state); + i0.ColumnFilters get apiId => $state.composableBuilder( + column: $state.table.apiId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get projectId => $state.composableBuilder( + column: $state.table.projectId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CelestApisOrderingComposer + extends i0.OrderingComposer { + $CelestApisOrderingComposer(super.$state); + i0.ColumnOrderings get apiId => $state.composableBuilder( + column: $state.table.apiId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get projectId => $state.composableBuilder( + column: $state.table.projectId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CelestApisTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CelestApis, + i1.CelestApi, + i1.$CelestApisFilterComposer, + i1.$CelestApisOrderingComposer, + $CelestApisCreateCompanionBuilder, + $CelestApisUpdateCompanionBuilder, + ( + i1.CelestApi, + i0.BaseReferences + ), + i1.CelestApi, + i0.PrefetchHooks Function()> { + $CelestApisTableManager(i0.GeneratedDatabase db, i1.CelestApis table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CelestApisFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CelestApisOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value apiId = const i0.Value.absent(), + i0.Value projectId = const i0.Value.absent(), + i0.Value resolvedAst = const i0.Value.absent(), + i0.Value etag = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestApisCompanion( + apiId: apiId, + projectId: projectId, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + createCompanionCallback: ({ + required String apiId, + required String projectId, + required i2.ResolvedApi resolvedAst, + required String etag, + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestApisCompanion.insert( + apiId: apiId, + projectId: projectId, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CelestApisProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CelestApis, + i1.CelestApi, + i1.$CelestApisFilterComposer, + i1.$CelestApisOrderingComposer, + $CelestApisCreateCompanionBuilder, + $CelestApisUpdateCompanionBuilder, + ( + i1.CelestApi, + i0.BaseReferences + ), + i1.CelestApi, + i0.PrefetchHooks Function()>; +i0.Index get celestApisProjectIdx => i0.Index('celest_apis_project_idx', + 'CREATE INDEX IF NOT EXISTS celest_apis_project_idx ON celest_apis (project_id)'); +i0.Trigger get celestApisTriggerCreate => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_create BEFORE INSERT ON celest_apis BEGIN INSERT INTO cedar_entities (entity_type, entity_id) VALUES (\'Celest::Api\', NEW.api_id);END', + 'celest_apis_trigger_create'); +i0.Trigger get celestApisTriggerDelete => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS celest_apis_trigger_delete AFTER DELETE ON celest_apis BEGIN DELETE FROM cedar_relationships WHERE entity_type = \'Celest::Api\' AND entity_id = OLD.api_id;DELETE FROM cedar_relationships WHERE parent_type = \'Celest::Api\' AND parent_id = OLD.api_id;DELETE FROM cedar_entities WHERE entity_type = \'Celest::Api\' AND entity_id = OLD.api_id;END', + 'celest_apis_trigger_delete'); + +class CelestFunctions extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + CelestFunctions(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn functionId = i0.GeneratedColumn( + 'function_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn apiId = i0.GeneratedColumn( + 'api_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0 + .GeneratedColumnWithTypeConverter + resolvedAst = i0.GeneratedColumn( + 'resolved_ast', aliasedName, false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter( + i1.CelestFunctions.$converterresolvedAst); + late final i0.GeneratedColumn etag = i0.GeneratedColumn( + 'etag', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => + [functionId, apiId, resolvedAst, etag]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'celest_functions'; + @override + Set get $primaryKey => {functionId}; + @override + i1.CelestFunction map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.CelestFunction( + functionId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}function_id'])!, + apiId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}api_id'])!, + resolvedAst: i1.CelestFunctions.$converterresolvedAst.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, data['${effectivePrefix}resolved_ast'])!), + etag: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}etag'])!, + ); + } + + @override + CelestFunctions createAlias(String alias) { + return CelestFunctions(attachedDatabase, alias); + } + + static i0.TypeConverter + $converterresolvedAst = const i4.ResolvedFunctionConverter(); + @override + List get customConstraints => const [ + 'CONSTRAINT celest_functions_api_fk FOREIGN KEY(api_id)REFERENCES celest_apis(api_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class CelestFunction extends i0.DataClass + implements i0.Insertable { + /// The unique identifier of the function. + final String functionId; + + /// The API ID of the function. + final String apiId; + + /// Format: Proto[celest.ast.v1.ResolvedFunction] + /// + /// The resolved function AST. + final i2.ResolvedCloudFunction resolvedAst; + + /// A hash of the function metadata. + final String etag; + const CelestFunction( + {required this.functionId, + required this.apiId, + required this.resolvedAst, + required this.etag}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['function_id'] = i0.Variable(functionId); + map['api_id'] = i0.Variable(apiId); + { + map['resolved_ast'] = i0.Variable( + i1.CelestFunctions.$converterresolvedAst.toSql(resolvedAst)); + } + map['etag'] = i0.Variable(etag); + return map; + } + + factory CelestFunction.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return CelestFunction( + functionId: serializer.fromJson(json['function_id']), + apiId: serializer.fromJson(json['api_id']), + resolvedAst: + serializer.fromJson(json['resolved_ast']), + etag: serializer.fromJson(json['etag']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'function_id': serializer.toJson(functionId), + 'api_id': serializer.toJson(apiId), + 'resolved_ast': serializer.toJson(resolvedAst), + 'etag': serializer.toJson(etag), + }; + } + + i1.CelestFunction copyWith( + {String? functionId, + String? apiId, + i2.ResolvedCloudFunction? resolvedAst, + String? etag}) => + i1.CelestFunction( + functionId: functionId ?? this.functionId, + apiId: apiId ?? this.apiId, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + ); + CelestFunction copyWithCompanion(i1.CelestFunctionsCompanion data) { + return CelestFunction( + functionId: + data.functionId.present ? data.functionId.value : this.functionId, + apiId: data.apiId.present ? data.apiId.value : this.apiId, + resolvedAst: + data.resolvedAst.present ? data.resolvedAst.value : this.resolvedAst, + etag: data.etag.present ? data.etag.value : this.etag, + ); + } + + @override + String toString() { + return (StringBuffer('CelestFunction(') + ..write('functionId: $functionId, ') + ..write('apiId: $apiId, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(functionId, apiId, resolvedAst, etag); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.CelestFunction && + other.functionId == this.functionId && + other.apiId == this.apiId && + other.resolvedAst == this.resolvedAst && + other.etag == this.etag); +} + +class CelestFunctionsCompanion extends i0.UpdateCompanion { + final i0.Value functionId; + final i0.Value apiId; + final i0.Value resolvedAst; + final i0.Value etag; + final i0.Value rowid; + const CelestFunctionsCompanion({ + this.functionId = const i0.Value.absent(), + this.apiId = const i0.Value.absent(), + this.resolvedAst = const i0.Value.absent(), + this.etag = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + CelestFunctionsCompanion.insert({ + required String functionId, + required String apiId, + required i2.ResolvedCloudFunction resolvedAst, + required String etag, + this.rowid = const i0.Value.absent(), + }) : functionId = i0.Value(functionId), + apiId = i0.Value(apiId), + resolvedAst = i0.Value(resolvedAst), + etag = i0.Value(etag); + static i0.Insertable custom({ + i0.Expression? functionId, + i0.Expression? apiId, + i0.Expression? resolvedAst, + i0.Expression? etag, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (functionId != null) 'function_id': functionId, + if (apiId != null) 'api_id': apiId, + if (resolvedAst != null) 'resolved_ast': resolvedAst, + if (etag != null) 'etag': etag, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.CelestFunctionsCompanion copyWith( + {i0.Value? functionId, + i0.Value? apiId, + i0.Value? resolvedAst, + i0.Value? etag, + i0.Value? rowid}) { + return i1.CelestFunctionsCompanion( + functionId: functionId ?? this.functionId, + apiId: apiId ?? this.apiId, + resolvedAst: resolvedAst ?? this.resolvedAst, + etag: etag ?? this.etag, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (functionId.present) { + map['function_id'] = i0.Variable(functionId.value); + } + if (apiId.present) { + map['api_id'] = i0.Variable(apiId.value); + } + if (resolvedAst.present) { + map['resolved_ast'] = i0.Variable( + i1.CelestFunctions.$converterresolvedAst.toSql(resolvedAst.value)); + } + if (etag.present) { + map['etag'] = i0.Variable(etag.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CelestFunctionsCompanion(') + ..write('functionId: $functionId, ') + ..write('apiId: $apiId, ') + ..write('resolvedAst: $resolvedAst, ') + ..write('etag: $etag, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $CelestFunctionsCreateCompanionBuilder = i1.CelestFunctionsCompanion + Function({ + required String functionId, + required String apiId, + required i2.ResolvedCloudFunction resolvedAst, + required String etag, + i0.Value rowid, +}); +typedef $CelestFunctionsUpdateCompanionBuilder = i1.CelestFunctionsCompanion + Function({ + i0.Value functionId, + i0.Value apiId, + i0.Value resolvedAst, + i0.Value etag, + i0.Value rowid, +}); + +class $CelestFunctionsFilterComposer + extends i0.FilterComposer { + $CelestFunctionsFilterComposer(super.$state); + i0.ColumnFilters get functionId => $state.composableBuilder( + column: $state.table.functionId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get apiId => $state.composableBuilder( + column: $state.table.apiId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnWithTypeConverterFilters + get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => i0.ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + i0.ColumnFilters get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $CelestFunctionsOrderingComposer + extends i0.OrderingComposer { + $CelestFunctionsOrderingComposer(super.$state); + i0.ColumnOrderings get functionId => $state.composableBuilder( + column: $state.table.functionId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get apiId => $state.composableBuilder( + column: $state.table.apiId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get resolvedAst => $state.composableBuilder( + column: $state.table.resolvedAst, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get etag => $state.composableBuilder( + column: $state.table.etag, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $CelestFunctionsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.CelestFunctions, + i1.CelestFunction, + i1.$CelestFunctionsFilterComposer, + i1.$CelestFunctionsOrderingComposer, + $CelestFunctionsCreateCompanionBuilder, + $CelestFunctionsUpdateCompanionBuilder, + ( + i1.CelestFunction, + i0.BaseReferences + ), + i1.CelestFunction, + i0.PrefetchHooks Function()> { + $CelestFunctionsTableManager( + i0.GeneratedDatabase db, i1.CelestFunctions table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i1.$CelestFunctionsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i1.$CelestFunctionsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value functionId = const i0.Value.absent(), + i0.Value apiId = const i0.Value.absent(), + i0.Value resolvedAst = + const i0.Value.absent(), + i0.Value etag = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestFunctionsCompanion( + functionId: functionId, + apiId: apiId, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + createCompanionCallback: ({ + required String functionId, + required String apiId, + required i2.ResolvedCloudFunction resolvedAst, + required String etag, + i0.Value rowid = const i0.Value.absent(), + }) => + i1.CelestFunctionsCompanion.insert( + functionId: functionId, + apiId: apiId, + resolvedAst: resolvedAst, + etag: etag, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $CelestFunctionsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.CelestFunctions, + i1.CelestFunction, + i1.$CelestFunctionsFilterComposer, + i1.$CelestFunctionsOrderingComposer, + $CelestFunctionsCreateCompanionBuilder, + $CelestFunctionsUpdateCompanionBuilder, + ( + i1.CelestFunction, + i0.BaseReferences + ), + i1.CelestFunction, + i0.PrefetchHooks Function()>; +i0.Index get celestFunctionsApiIdx => i0.Index('celest_functions_api_idx', + 'CREATE INDEX IF NOT EXISTS celest_functions_api_idx ON celest_functions (api_id)'); +i0.Trigger get celestFunctionsTriggerCreate => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_create BEFORE INSERT ON celest_functions BEGIN INSERT INTO cedar_entities (entity_type, entity_id) VALUES (\'Celest::Function\', NEW.function_id);INSERT INTO cedar_relationships (entity_type, entity_id, parent_type, parent_id) VALUES (\'Celest::Function\', NEW.function_id, \'Celest::Api\', NEW.api_id);END', + 'celest_functions_trigger_create'); +i0.Trigger get celestFunctionsTriggerDelete => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS celest_functions_trigger_delete AFTER DELETE ON celest_functions BEGIN DELETE FROM cedar_relationships WHERE entity_type = \'Celest::Function\' AND entity_id = OLD.function_id;DELETE FROM cedar_relationships WHERE parent_type = \'Celest::Function\' AND parent_id = OLD.function_id;DELETE FROM cedar_entities WHERE entity_type = \'Celest::Function\' AND entity_id = OLD.function_id;END', + 'celest_functions_trigger_delete'); + +class ProjectsDrift extends i5.ModularAccessor { + ProjectsDrift(i0.GeneratedDatabase db) : super(db); + i6.Future> upsertProject( + {required String projectId, + required String version, + required i2.ResolvedProject resolvedAst, + required String etag}) { + return customWriteReturning( + 'INSERT INTO celest_projects (project_id, version, resolved_ast, etag) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (project_id) DO UPDATE SET version = excluded.version, resolved_ast = excluded.resolved_ast, etag = excluded.etag RETURNING *', + variables: [ + i0.Variable(projectId), + i0.Variable(version), + i0.Variable( + i1.CelestProjects.$converterresolvedAst.toSql(resolvedAst)), + i0.Variable(etag) + ], + updates: { + celestProjects + }).then((rows) => Future.wait(rows.map(celestProjects.mapFromRow))); + } + + i0.Selectable getProject({required String projectId}) { + return customSelect('SELECT * FROM celest_projects WHERE project_id = ?1', + variables: [ + i0.Variable(projectId) + ], + readsFrom: { + celestProjects, + }).asyncMap(celestProjects.mapFromRow); + } + + i6.Future> upsertApi( + {required String apiId, + required String projectId, + required i2.ResolvedApi resolvedAst, + required String etag}) { + return customWriteReturning( + 'INSERT INTO celest_apis (api_id, project_id, resolved_ast, etag) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (api_id) DO UPDATE SET project_id = excluded.project_id, resolved_ast = excluded.resolved_ast, etag = excluded.etag RETURNING *', + variables: [ + i0.Variable(apiId), + i0.Variable(projectId), + i0.Variable( + i1.CelestApis.$converterresolvedAst.toSql(resolvedAst)), + i0.Variable(etag) + ], + updates: { + celestApis + }).then((rows) => Future.wait(rows.map(celestApis.mapFromRow))); + } + + i0.Selectable getApi({required String apiId}) { + return customSelect('SELECT * FROM celest_apis WHERE api_id = ?1', + variables: [ + i0.Variable(apiId) + ], + readsFrom: { + celestApis, + }).asyncMap(celestApis.mapFromRow); + } + + i0.Selectable listApis({required String projectId}) { + return customSelect('SELECT * FROM celest_apis WHERE project_id = ?1', + variables: [ + i0.Variable(projectId) + ], + readsFrom: { + celestApis, + }).asyncMap(celestApis.mapFromRow); + } + + i6.Future> upsertFunction( + {required String functionId, + required String apiId, + required i2.ResolvedCloudFunction resolvedAst, + required String etag}) { + return customWriteReturning( + 'INSERT INTO celest_functions (function_id, api_id, resolved_ast, etag) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (function_id) DO UPDATE SET api_id = excluded.api_id, resolved_ast = excluded.resolved_ast, etag = excluded.etag RETURNING *', + variables: [ + i0.Variable(functionId), + i0.Variable(apiId), + i0.Variable( + i1.CelestFunctions.$converterresolvedAst.toSql(resolvedAst)), + i0.Variable(etag) + ], + updates: { + celestFunctions + }).then((rows) => Future.wait(rows.map(celestFunctions.mapFromRow))); + } + + i0.Selectable getFunction({required String functionId}) { + return customSelect('SELECT * FROM celest_functions WHERE function_id = ?1', + variables: [ + i0.Variable(functionId) + ], + readsFrom: { + celestFunctions, + }).asyncMap(celestFunctions.mapFromRow); + } + + i0.Selectable listFunctions({required String apiId}) { + return customSelect('SELECT * FROM celest_functions WHERE api_id = ?1', + variables: [ + i0.Variable(apiId) + ], + readsFrom: { + celestFunctions, + }).asyncMap(celestFunctions.mapFromRow); + } + + i1.CelestProjects get celestProjects => + i5.ReadDatabaseContainer(attachedDatabase) + .resultSet('celest_projects'); + i1.CelestApis get celestApis => i5.ReadDatabaseContainer(attachedDatabase) + .resultSet('celest_apis'); + i1.CelestFunctions get celestFunctions => + i5.ReadDatabaseContainer(attachedDatabase) + .resultSet('celest_functions'); +} diff --git a/services/celest_cloud_auth/lib/src/database/schema/schema_imports.dart b/services/celest_cloud_auth/lib/src/database/schema/schema_imports.dart new file mode 100644 index 00000000..9c9f6278 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/schema_imports.dart @@ -0,0 +1,7 @@ +export 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +export 'package:celest_cloud_auth/src/crypto/crypto_key_model.dart'; +export 'package:celest_cloud_auth/src/database/database_model.dart'; +export 'package:celest_cloud_auth/src/database/schema/converters/ast_converters.dart'; +export 'package:celest_cloud_auth/src/database/schema/converters/auth_converters.dart'; +export 'package:celest_cloud_auth/src/database/schema/converters/cedar_converters.dart'; +export 'package:celest_cloud_auth/src/database/schema/converters/celest_converters.dart'; diff --git a/services/celest_cloud_auth/lib/src/database/schema/users.drift b/services/celest_cloud_auth/lib/src/database/schema/users.drift new file mode 100644 index 00000000..e6dfd14d --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/users.drift @@ -0,0 +1,176 @@ +import 'cedar.drift'; + +import 'schema_imports.dart'; + +-- Stores Celest Cloud users. +CREATE TABLE IF NOT EXISTS users ( + -- Immutable. The unique identifier for the user. + -- + -- Maps to the `uid` field in the Protobuf. + -- + -- Format: usr_ + user_id TEXT NOT NULL PRIMARY KEY, + + -- The user's given name. + given_name TEXT, + + -- The user's family name. + family_name TEXT, + + -- The user's time zone. + -- + -- Format: IANA time zone identifier + -- Example: `America/Los_Angeles` + time_zone TEXT, + + -- The user's preferred language. + -- + -- Format: BCP-47 language tag + -- Example: `en-US` + language_code TEXT, + + -- The time the user was created. + create_time `const TimestampType()` NOT NULL DEFAULT (unixepoch('now', 'subsec')), + + -- The time the user was last updated. + update_time `const TimestampType()` +) WITH User; + +---------------------------------------------- +-------------- Cedar Triggers ---------------- +---------------------------------------------- + +-- This trigger is used to create the Cedar entity when a row is inserted. +CREATE TRIGGER IF NOT EXISTS users_create +BEFORE INSERT ON users +BEGIN + INSERT INTO cedar_entities(entity_type, entity_id) + VALUES ('Celest::User', NEW.user_id); +END; + +-- This trigger is used to delete the parent relationship when the parent_id is set to NULL. +CREATE TRIGGER IF NOT EXISTS users_delete +AFTER DELETE ON users +BEGIN + DELETE FROM cedar_relationships + WHERE + (entity_type = 'Celest::User' AND entity_id = OLD.user_id) + OR (parent_type = 'Celest::User' AND parent_id = OLD.user_id); + DELETE FROM cedar_entities + WHERE + entity_id = OLD.user_id + AND entity_type = 'Celest::User'; +END; + +---------------------------------------------- +-------------- /Cedar Triggers --------------- +---------------------------------------------- + +-- Stores email addresses for Celest Cloud users. +CREATE TABLE IF NOT EXISTS user_emails ( + -- The user's unique identifier. + user_id TEXT NOT NULL, + + -- The user's sanitized email address. + email TEXT NOT NULL, + + -- The email address's verification status. + is_verified BOOLEAN NOT NULL DEFAULT FALSE, + + -- The email address's primary status. + is_primary BOOLEAN NOT NULL DEFAULT FALSE, + + CONSTRAINT user_emails_pk PRIMARY KEY (user_id, email), + + CONSTRAINT user_emails_user_fk FOREIGN KEY (user_id) REFERENCES users(user_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +) WITHOUT ROWID WITH Email; + +-- Stores phone numbers for Celest Cloud users. +CREATE TABLE IF NOT EXISTS user_phone_numbers ( + -- The user's unique identifier. + user_id TEXT NOT NULL, + + -- The user's sanitized phone number. + phone_number TEXT NOT NULL, + + -- The phone number's verification status. + is_verified BOOLEAN NOT NULL DEFAULT FALSE, + + -- The phone number's primary status. + is_primary BOOLEAN NOT NULL DEFAULT FALSE, + + CONSTRAINT user_phone_numbers_pk PRIMARY KEY (user_id, phone_number), + + CONSTRAINT user_phone_numbers_user_fk FOREIGN KEY (user_id) REFERENCES users(user_id) + ON UPDATE CASCADE ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +) WITHOUT ROWID WITH PhoneNumber; + +createUser: + INSERT INTO users (user_id, given_name, family_name, time_zone, language_code) + VALUES (:user_id, :given_name, :family_name, :time_zone, :language_code) + RETURNING *; + +getUser: + SELECT * FROM users + WHERE user_id = :user_id; + +deleteUser: + DELETE FROM users + WHERE user_id = :user_id; + +upsertUserEmail( + :is_verified AS BOOLEAN OR NULL, + :is_primary AS BOOLEAN OR NULL +): + INSERT INTO user_emails (user_id, email, is_verified, is_primary) + VALUES (:user_id, :email, :is_verified, :is_primary) + ON CONFLICT(user_id, email) DO UPDATE SET + is_verified = coalesce(excluded.is_verified, is_verified), + is_primary = coalesce(excluded.is_primary, is_primary) + RETURNING *; + +getUserEmails: + SELECT * FROM user_emails + WHERE user_id = :user_id; + +lookupUserByEmail: + SELECT users.**, user_emails.** FROM users + INNER JOIN user_emails ON users.user_id = user_emails.user_id + WHERE + user_emails.email = :email + AND user_emails.is_verified + ORDER BY user_emails.is_primary DESC; + +deleteUserEmail: + DELETE FROM user_emails + WHERE user_id = :user_id AND email = :email; + +upsertUserPhoneNumber( + :is_verified AS BOOLEAN OR NULL, + :is_primary AS BOOLEAN OR NULL +): + INSERT INTO user_phone_numbers (user_id, phone_number, is_verified, is_primary) + VALUES (:user_id, :phone_number, :is_verified, :is_primary) + ON CONFLICT(user_id, phone_number) DO UPDATE SET + is_verified = coalesce(excluded.is_verified, is_verified), + is_primary = coalesce(excluded.is_primary, is_primary) + RETURNING *; + +getUserPhoneNumbers: + SELECT * FROM user_phone_numbers + WHERE user_id = :user_id; + +lookupUserByPhone: + SELECT users.**, user_phone_numbers.** FROM users + INNER JOIN user_phone_numbers ON users.user_id = user_phone_numbers.user_id + WHERE + user_phone_numbers.phone_number = :phone_number + AND user_phone_numbers.is_verified + ORDER BY user_phone_numbers.is_primary DESC; + +deleteUserPhoneNumber: + DELETE FROM user_phone_numbers + WHERE user_id = :user_id AND phone_number = :phone_number; diff --git a/services/celest_cloud_auth/lib/src/database/schema/users.drift.dart b/services/celest_cloud_auth/lib/src/database/schema/users.drift.dart new file mode 100644 index 00000000..7a078302 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema/users.drift.dart @@ -0,0 +1,1124 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:celest_core/src/auth/user.dart' as i1; +import 'package:celest_cloud_auth/src/database/schema/users.drift.dart' as i2; +import 'package:celest_cloud_auth/src/database/database_model.dart' as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'dart:async' as i5; + +class Users extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Users(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL PRIMARY KEY'); + late final i0.GeneratedColumn givenName = i0.GeneratedColumn( + 'given_name', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn familyName = i0.GeneratedColumn( + 'family_name', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn timeZone = i0.GeneratedColumn( + 'time_zone', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn languageCode = + i0.GeneratedColumn('language_code', aliasedName, true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: ''); + late final i0.GeneratedColumn createTime = + i0.GeneratedColumn( + 'create_time', aliasedName, false, + type: const i3.TimestampType(), + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT (unixepoch(\'now\', \'subsec\'))', + defaultValue: + const i0.CustomExpression('unixepoch(\'now\', \'subsec\')')); + late final i0.GeneratedColumn updateTime = + i0.GeneratedColumn('update_time', aliasedName, true, + type: const i3.TimestampType(), + requiredDuringInsert: false, + $customConstraints: ''); + @override + List get $columns => [ + userId, + givenName, + familyName, + timeZone, + languageCode, + createTime, + updateTime + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'users'; + @override + Set get $primaryKey => {userId}; + @override + i1.User map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.User( + userId: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}user_id'])!, + createTime: attachedDatabase.typeMapping.read( + const i3.TimestampType(), data['${effectivePrefix}create_time'])!, + updateTime: attachedDatabase.typeMapping.read( + const i3.TimestampType(), data['${effectivePrefix}update_time']), + givenName: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}given_name']), + familyName: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}family_name']), + timeZone: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}time_zone']), + languageCode: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}language_code']), + ); + } + + @override + Users createAlias(String alias) { + return Users(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UsersCompanion extends i0.UpdateCompanion { + final i0.Value userId; + final i0.Value givenName; + final i0.Value familyName; + final i0.Value timeZone; + final i0.Value languageCode; + final i0.Value createTime; + final i0.Value updateTime; + final i0.Value rowid; + const UsersCompanion({ + this.userId = const i0.Value.absent(), + this.givenName = const i0.Value.absent(), + this.familyName = const i0.Value.absent(), + this.timeZone = const i0.Value.absent(), + this.languageCode = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + UsersCompanion.insert({ + required String userId, + this.givenName = const i0.Value.absent(), + this.familyName = const i0.Value.absent(), + this.timeZone = const i0.Value.absent(), + this.languageCode = const i0.Value.absent(), + this.createTime = const i0.Value.absent(), + this.updateTime = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }) : userId = i0.Value(userId); + static i0.Insertable custom({ + i0.Expression? userId, + i0.Expression? givenName, + i0.Expression? familyName, + i0.Expression? timeZone, + i0.Expression? languageCode, + i0.Expression? createTime, + i0.Expression? updateTime, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (givenName != null) 'given_name': givenName, + if (familyName != null) 'family_name': familyName, + if (timeZone != null) 'time_zone': timeZone, + if (languageCode != null) 'language_code': languageCode, + if (createTime != null) 'create_time': createTime, + if (updateTime != null) 'update_time': updateTime, + if (rowid != null) 'rowid': rowid, + }); + } + + i2.UsersCompanion copyWith( + {i0.Value? userId, + i0.Value? givenName, + i0.Value? familyName, + i0.Value? timeZone, + i0.Value? languageCode, + i0.Value? createTime, + i0.Value? updateTime, + i0.Value? rowid}) { + return i2.UsersCompanion( + userId: userId ?? this.userId, + givenName: givenName ?? this.givenName, + familyName: familyName ?? this.familyName, + timeZone: timeZone ?? this.timeZone, + languageCode: languageCode ?? this.languageCode, + createTime: createTime ?? this.createTime, + updateTime: updateTime ?? this.updateTime, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (givenName.present) { + map['given_name'] = i0.Variable(givenName.value); + } + if (familyName.present) { + map['family_name'] = i0.Variable(familyName.value); + } + if (timeZone.present) { + map['time_zone'] = i0.Variable(timeZone.value); + } + if (languageCode.present) { + map['language_code'] = i0.Variable(languageCode.value); + } + if (createTime.present) { + map['create_time'] = + i0.Variable(createTime.value, const i3.TimestampType()); + } + if (updateTime.present) { + map['update_time'] = + i0.Variable(updateTime.value, const i3.TimestampType()); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('userId: $userId, ') + ..write('givenName: $givenName, ') + ..write('familyName: $familyName, ') + ..write('timeZone: $timeZone, ') + ..write('languageCode: $languageCode, ') + ..write('createTime: $createTime, ') + ..write('updateTime: $updateTime, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +typedef $UsersCreateCompanionBuilder = i2.UsersCompanion Function({ + required String userId, + i0.Value givenName, + i0.Value familyName, + i0.Value timeZone, + i0.Value languageCode, + i0.Value createTime, + i0.Value updateTime, + i0.Value rowid, +}); +typedef $UsersUpdateCompanionBuilder = i2.UsersCompanion Function({ + i0.Value userId, + i0.Value givenName, + i0.Value familyName, + i0.Value timeZone, + i0.Value languageCode, + i0.Value createTime, + i0.Value updateTime, + i0.Value rowid, +}); + +class $UsersFilterComposer + extends i0.FilterComposer { + $UsersFilterComposer(super.$state); + i0.ColumnFilters get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get givenName => $state.composableBuilder( + column: $state.table.givenName, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get familyName => $state.composableBuilder( + column: $state.table.familyName, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get timeZone => $state.composableBuilder( + column: $state.table.timeZone, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get languageCode => $state.composableBuilder( + column: $state.table.languageCode, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $UsersOrderingComposer + extends i0.OrderingComposer { + $UsersOrderingComposer(super.$state); + i0.ColumnOrderings get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get givenName => $state.composableBuilder( + column: $state.table.givenName, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get familyName => $state.composableBuilder( + column: $state.table.familyName, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get timeZone => $state.composableBuilder( + column: $state.table.timeZone, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get languageCode => $state.composableBuilder( + column: $state.table.languageCode, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get createTime => $state.composableBuilder( + column: $state.table.createTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get updateTime => $state.composableBuilder( + column: $state.table.updateTime, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $UsersTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.Users, + i1.User, + i2.$UsersFilterComposer, + i2.$UsersOrderingComposer, + $UsersCreateCompanionBuilder, + $UsersUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()> { + $UsersTableManager(i0.GeneratedDatabase db, i2.Users table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i2.$UsersFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i2.$UsersOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value userId = const i0.Value.absent(), + i0.Value givenName = const i0.Value.absent(), + i0.Value familyName = const i0.Value.absent(), + i0.Value timeZone = const i0.Value.absent(), + i0.Value languageCode = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i2.UsersCompanion( + userId: userId, + givenName: givenName, + familyName: familyName, + timeZone: timeZone, + languageCode: languageCode, + createTime: createTime, + updateTime: updateTime, + rowid: rowid, + ), + createCompanionCallback: ({ + required String userId, + i0.Value givenName = const i0.Value.absent(), + i0.Value familyName = const i0.Value.absent(), + i0.Value timeZone = const i0.Value.absent(), + i0.Value languageCode = const i0.Value.absent(), + i0.Value createTime = const i0.Value.absent(), + i0.Value updateTime = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i2.UsersCompanion.insert( + userId: userId, + givenName: givenName, + familyName: familyName, + timeZone: timeZone, + languageCode: languageCode, + createTime: createTime, + updateTime: updateTime, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $UsersProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.Users, + i1.User, + i2.$UsersFilterComposer, + i2.$UsersOrderingComposer, + $UsersCreateCompanionBuilder, + $UsersUpdateCompanionBuilder, + (i1.User, i0.BaseReferences), + i1.User, + i0.PrefetchHooks Function()>; +i0.Trigger get usersCreate => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS users_create BEFORE INSERT ON users BEGIN INSERT INTO cedar_entities (entity_type, entity_id) VALUES (\'Celest::User\', NEW.user_id);END', + 'users_create'); +i0.Trigger get usersDelete => i0.Trigger( + 'CREATE TRIGGER IF NOT EXISTS users_delete AFTER DELETE ON users BEGIN DELETE FROM cedar_relationships WHERE(entity_type = \'Celest::User\' AND entity_id = OLD.user_id)OR(parent_type = \'Celest::User\' AND parent_id = OLD.user_id);DELETE FROM cedar_entities WHERE entity_id = OLD.user_id AND entity_type = \'Celest::User\';END', + 'users_delete'); + +class UserEmails extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + UserEmails(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn email = i0.GeneratedColumn( + 'email', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn isVerified = i0.GeneratedColumn( + 'is_verified', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE')); + late final i0.GeneratedColumn isPrimary = i0.GeneratedColumn( + 'is_primary', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE')); + @override + List get $columns => + [userId, email, isVerified, isPrimary]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_emails'; + @override + Set get $primaryKey => {userId, email}; + @override + i1.Email map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Email( + email: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}email'])!, + isVerified: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}is_verified'])!, + isPrimary: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}is_primary'])!, + ); + } + + @override + UserEmails createAlias(String alias) { + return UserEmails(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + List get customConstraints => const [ + 'CONSTRAINT user_emails_pk PRIMARY KEY(user_id, email)', + 'CONSTRAINT user_emails_user_fk FOREIGN KEY(user_id)REFERENCES users(user_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class UserEmailsCompanion extends i0.UpdateCompanion { + final i0.Value userId; + final i0.Value email; + final i0.Value isVerified; + final i0.Value isPrimary; + const UserEmailsCompanion({ + this.userId = const i0.Value.absent(), + this.email = const i0.Value.absent(), + this.isVerified = const i0.Value.absent(), + this.isPrimary = const i0.Value.absent(), + }); + UserEmailsCompanion.insert({ + required String userId, + required String email, + this.isVerified = const i0.Value.absent(), + this.isPrimary = const i0.Value.absent(), + }) : userId = i0.Value(userId), + email = i0.Value(email); + static i0.Insertable custom({ + i0.Expression? userId, + i0.Expression? email, + i0.Expression? isVerified, + i0.Expression? isPrimary, + }) { + return i0.RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (email != null) 'email': email, + if (isVerified != null) 'is_verified': isVerified, + if (isPrimary != null) 'is_primary': isPrimary, + }); + } + + i2.UserEmailsCompanion copyWith( + {i0.Value? userId, + i0.Value? email, + i0.Value? isVerified, + i0.Value? isPrimary}) { + return i2.UserEmailsCompanion( + userId: userId ?? this.userId, + email: email ?? this.email, + isVerified: isVerified ?? this.isVerified, + isPrimary: isPrimary ?? this.isPrimary, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (email.present) { + map['email'] = i0.Variable(email.value); + } + if (isVerified.present) { + map['is_verified'] = i0.Variable(isVerified.value); + } + if (isPrimary.present) { + map['is_primary'] = i0.Variable(isPrimary.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEmailsCompanion(') + ..write('userId: $userId, ') + ..write('email: $email, ') + ..write('isVerified: $isVerified, ') + ..write('isPrimary: $isPrimary') + ..write(')')) + .toString(); + } +} + +typedef $UserEmailsCreateCompanionBuilder = i2.UserEmailsCompanion Function({ + required String userId, + required String email, + i0.Value isVerified, + i0.Value isPrimary, +}); +typedef $UserEmailsUpdateCompanionBuilder = i2.UserEmailsCompanion Function({ + i0.Value userId, + i0.Value email, + i0.Value isVerified, + i0.Value isPrimary, +}); + +class $UserEmailsFilterComposer + extends i0.FilterComposer { + $UserEmailsFilterComposer(super.$state); + i0.ColumnFilters get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get email => $state.composableBuilder( + column: $state.table.email, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get isVerified => $state.composableBuilder( + column: $state.table.isVerified, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get isPrimary => $state.composableBuilder( + column: $state.table.isPrimary, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $UserEmailsOrderingComposer + extends i0.OrderingComposer { + $UserEmailsOrderingComposer(super.$state); + i0.ColumnOrderings get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get email => $state.composableBuilder( + column: $state.table.email, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get isVerified => $state.composableBuilder( + column: $state.table.isVerified, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get isPrimary => $state.composableBuilder( + column: $state.table.isPrimary, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $UserEmailsTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.UserEmails, + i1.Email, + i2.$UserEmailsFilterComposer, + i2.$UserEmailsOrderingComposer, + $UserEmailsCreateCompanionBuilder, + $UserEmailsUpdateCompanionBuilder, + ( + i1.Email, + i0.BaseReferences + ), + i1.Email, + i0.PrefetchHooks Function()> { + $UserEmailsTableManager(i0.GeneratedDatabase db, i2.UserEmails table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i2.$UserEmailsFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i2.$UserEmailsOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value userId = const i0.Value.absent(), + i0.Value email = const i0.Value.absent(), + i0.Value isVerified = const i0.Value.absent(), + i0.Value isPrimary = const i0.Value.absent(), + }) => + i2.UserEmailsCompanion( + userId: userId, + email: email, + isVerified: isVerified, + isPrimary: isPrimary, + ), + createCompanionCallback: ({ + required String userId, + required String email, + i0.Value isVerified = const i0.Value.absent(), + i0.Value isPrimary = const i0.Value.absent(), + }) => + i2.UserEmailsCompanion.insert( + userId: userId, + email: email, + isVerified: isVerified, + isPrimary: isPrimary, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $UserEmailsProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.UserEmails, + i1.Email, + i2.$UserEmailsFilterComposer, + i2.$UserEmailsOrderingComposer, + $UserEmailsCreateCompanionBuilder, + $UserEmailsUpdateCompanionBuilder, + ( + i1.Email, + i0.BaseReferences + ), + i1.Email, + i0.PrefetchHooks Function()>; + +class UserPhoneNumbers extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + UserPhoneNumbers(this.attachedDatabase, [this._alias]); + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn phoneNumber = + i0.GeneratedColumn('phone_number', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + late final i0.GeneratedColumn isVerified = i0.GeneratedColumn( + 'is_verified', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE')); + late final i0.GeneratedColumn isPrimary = i0.GeneratedColumn( + 'is_primary', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT FALSE', + defaultValue: const i0.CustomExpression('FALSE')); + @override + List get $columns => + [userId, phoneNumber, isVerified, isPrimary]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_phone_numbers'; + @override + Set get $primaryKey => {userId, phoneNumber}; + @override + i1.PhoneNumber map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.PhoneNumber( + phoneNumber: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, data['${effectivePrefix}phone_number'])!, + isVerified: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}is_verified'])!, + isPrimary: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}is_primary'])!, + ); + } + + @override + UserPhoneNumbers createAlias(String alias) { + return UserPhoneNumbers(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + List get customConstraints => const [ + 'CONSTRAINT user_phone_numbers_pk PRIMARY KEY(user_id, phone_number)', + 'CONSTRAINT user_phone_numbers_user_fk FOREIGN KEY(user_id)REFERENCES users(user_id)ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED' + ]; + @override + bool get dontWriteConstraints => true; +} + +class UserPhoneNumbersCompanion extends i0.UpdateCompanion { + final i0.Value userId; + final i0.Value phoneNumber; + final i0.Value isVerified; + final i0.Value isPrimary; + const UserPhoneNumbersCompanion({ + this.userId = const i0.Value.absent(), + this.phoneNumber = const i0.Value.absent(), + this.isVerified = const i0.Value.absent(), + this.isPrimary = const i0.Value.absent(), + }); + UserPhoneNumbersCompanion.insert({ + required String userId, + required String phoneNumber, + this.isVerified = const i0.Value.absent(), + this.isPrimary = const i0.Value.absent(), + }) : userId = i0.Value(userId), + phoneNumber = i0.Value(phoneNumber); + static i0.Insertable custom({ + i0.Expression? userId, + i0.Expression? phoneNumber, + i0.Expression? isVerified, + i0.Expression? isPrimary, + }) { + return i0.RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (phoneNumber != null) 'phone_number': phoneNumber, + if (isVerified != null) 'is_verified': isVerified, + if (isPrimary != null) 'is_primary': isPrimary, + }); + } + + i2.UserPhoneNumbersCompanion copyWith( + {i0.Value? userId, + i0.Value? phoneNumber, + i0.Value? isVerified, + i0.Value? isPrimary}) { + return i2.UserPhoneNumbersCompanion( + userId: userId ?? this.userId, + phoneNumber: phoneNumber ?? this.phoneNumber, + isVerified: isVerified ?? this.isVerified, + isPrimary: isPrimary ?? this.isPrimary, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (phoneNumber.present) { + map['phone_number'] = i0.Variable(phoneNumber.value); + } + if (isVerified.present) { + map['is_verified'] = i0.Variable(isVerified.value); + } + if (isPrimary.present) { + map['is_primary'] = i0.Variable(isPrimary.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserPhoneNumbersCompanion(') + ..write('userId: $userId, ') + ..write('phoneNumber: $phoneNumber, ') + ..write('isVerified: $isVerified, ') + ..write('isPrimary: $isPrimary') + ..write(')')) + .toString(); + } +} + +typedef $UserPhoneNumbersCreateCompanionBuilder = i2.UserPhoneNumbersCompanion + Function({ + required String userId, + required String phoneNumber, + i0.Value isVerified, + i0.Value isPrimary, +}); +typedef $UserPhoneNumbersUpdateCompanionBuilder = i2.UserPhoneNumbersCompanion + Function({ + i0.Value userId, + i0.Value phoneNumber, + i0.Value isVerified, + i0.Value isPrimary, +}); + +class $UserPhoneNumbersFilterComposer + extends i0.FilterComposer { + $UserPhoneNumbersFilterComposer(super.$state); + i0.ColumnFilters get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get phoneNumber => $state.composableBuilder( + column: $state.table.phoneNumber, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get isVerified => $state.composableBuilder( + column: $state.table.isVerified, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); + + i0.ColumnFilters get isPrimary => $state.composableBuilder( + column: $state.table.isPrimary, + builder: (column, joinBuilders) => + i0.ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $UserPhoneNumbersOrderingComposer + extends i0.OrderingComposer { + $UserPhoneNumbersOrderingComposer(super.$state); + i0.ColumnOrderings get userId => $state.composableBuilder( + column: $state.table.userId, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get phoneNumber => $state.composableBuilder( + column: $state.table.phoneNumber, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get isVerified => $state.composableBuilder( + column: $state.table.isVerified, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); + + i0.ColumnOrderings get isPrimary => $state.composableBuilder( + column: $state.table.isPrimary, + builder: (column, joinBuilders) => + i0.ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +class $UserPhoneNumbersTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.UserPhoneNumbers, + i1.PhoneNumber, + i2.$UserPhoneNumbersFilterComposer, + i2.$UserPhoneNumbersOrderingComposer, + $UserPhoneNumbersCreateCompanionBuilder, + $UserPhoneNumbersUpdateCompanionBuilder, + ( + i1.PhoneNumber, + i0 + .BaseReferences + ), + i1.PhoneNumber, + i0.PrefetchHooks Function()> { + $UserPhoneNumbersTableManager( + i0.GeneratedDatabase db, i2.UserPhoneNumbers table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + i2.$UserPhoneNumbersFilterComposer(i0.ComposerState(db, table)), + orderingComposer: + i2.$UserPhoneNumbersOrderingComposer(i0.ComposerState(db, table)), + updateCompanionCallback: ({ + i0.Value userId = const i0.Value.absent(), + i0.Value phoneNumber = const i0.Value.absent(), + i0.Value isVerified = const i0.Value.absent(), + i0.Value isPrimary = const i0.Value.absent(), + }) => + i2.UserPhoneNumbersCompanion( + userId: userId, + phoneNumber: phoneNumber, + isVerified: isVerified, + isPrimary: isPrimary, + ), + createCompanionCallback: ({ + required String userId, + required String phoneNumber, + i0.Value isVerified = const i0.Value.absent(), + i0.Value isPrimary = const i0.Value.absent(), + }) => + i2.UserPhoneNumbersCompanion.insert( + userId: userId, + phoneNumber: phoneNumber, + isVerified: isVerified, + isPrimary: isPrimary, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $UserPhoneNumbersProcessedTableManager = i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.UserPhoneNumbers, + i1.PhoneNumber, + i2.$UserPhoneNumbersFilterComposer, + i2.$UserPhoneNumbersOrderingComposer, + $UserPhoneNumbersCreateCompanionBuilder, + $UserPhoneNumbersUpdateCompanionBuilder, + ( + i1.PhoneNumber, + i0 + .BaseReferences + ), + i1.PhoneNumber, + i0.PrefetchHooks Function()>; + +class UsersDrift extends i4.ModularAccessor { + UsersDrift(i0.GeneratedDatabase db) : super(db); + i5.Future> createUser( + {required String userId, + String? givenName, + String? familyName, + String? timeZone, + String? languageCode}) { + return customWriteReturning( + 'INSERT INTO users (user_id, given_name, family_name, time_zone, language_code) VALUES (?1, ?2, ?3, ?4, ?5) RETURNING *', + variables: [ + i0.Variable(userId), + i0.Variable(givenName), + i0.Variable(familyName), + i0.Variable(timeZone), + i0.Variable(languageCode) + ], + updates: { + users + }).then((rows) => Future.wait(rows.map(users.mapFromRow))); + } + + i0.Selectable getUser({required String userId}) { + return customSelect('SELECT * FROM users WHERE user_id = ?1', variables: [ + i0.Variable(userId) + ], readsFrom: { + users, + }).asyncMap(users.mapFromRow); + } + + Future deleteUser({required String userId}) { + return customUpdate( + 'DELETE FROM users WHERE user_id = ?1', + variables: [i0.Variable(userId)], + updates: {users}, + updateKind: i0.UpdateKind.delete, + ); + } + + i5.Future> upsertUserEmail( + {required String userId, + required String email, + bool? isVerified, + bool? isPrimary}) { + return customWriteReturning( + 'INSERT INTO user_emails (user_id, email, is_verified, is_primary) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (user_id, email) DO UPDATE SET is_verified = coalesce(excluded.is_verified, is_verified), is_primary = coalesce(excluded.is_primary, is_primary) RETURNING *', + variables: [ + i0.Variable(userId), + i0.Variable(email), + i0.Variable(isVerified), + i0.Variable(isPrimary) + ], + updates: { + userEmails + }).then((rows) => Future.wait(rows.map(userEmails.mapFromRow))); + } + + i0.Selectable getUserEmails({required String userId}) { + return customSelect('SELECT * FROM user_emails WHERE user_id = ?1', + variables: [ + i0.Variable(userId) + ], + readsFrom: { + userEmails, + }).asyncMap(userEmails.mapFromRow); + } + + i0.Selectable lookupUserByEmail( + {required String email}) { + return customSelect( + 'SELECT"users"."user_id" AS "nested_0.user_id", "users"."given_name" AS "nested_0.given_name", "users"."family_name" AS "nested_0.family_name", "users"."time_zone" AS "nested_0.time_zone", "users"."language_code" AS "nested_0.language_code", "users"."create_time" AS "nested_0.create_time", "users"."update_time" AS "nested_0.update_time","user_emails"."user_id" AS "nested_1.user_id", "user_emails"."email" AS "nested_1.email", "user_emails"."is_verified" AS "nested_1.is_verified", "user_emails"."is_primary" AS "nested_1.is_primary" FROM users INNER JOIN user_emails ON users.user_id = user_emails.user_id WHERE user_emails.email = ?1 AND user_emails.is_verified ORDER BY user_emails.is_primary DESC', + variables: [ + i0.Variable(email) + ], + readsFrom: { + users, + userEmails, + }).asyncMap((i0.QueryRow row) async => LookupUserByEmailResult( + users: await users.mapFromRow(row, tablePrefix: 'nested_0'), + userEmails: await userEmails.mapFromRow(row, tablePrefix: 'nested_1'), + )); + } + + Future deleteUserEmail({required String userId, required String email}) { + return customUpdate( + 'DELETE FROM user_emails WHERE user_id = ?1 AND email = ?2', + variables: [i0.Variable(userId), i0.Variable(email)], + updates: {userEmails}, + updateKind: i0.UpdateKind.delete, + ); + } + + i5.Future> upsertUserPhoneNumber( + {required String userId, + required String phoneNumber, + bool? isVerified, + bool? isPrimary}) { + return customWriteReturning( + 'INSERT INTO user_phone_numbers (user_id, phone_number, is_verified, is_primary) VALUES (?1, ?2, ?3, ?4) ON CONFLICT (user_id, phone_number) DO UPDATE SET is_verified = coalesce(excluded.is_verified, is_verified), is_primary = coalesce(excluded.is_primary, is_primary) RETURNING *', + variables: [ + i0.Variable(userId), + i0.Variable(phoneNumber), + i0.Variable(isVerified), + i0.Variable(isPrimary) + ], + updates: { + userPhoneNumbers + }).then((rows) => Future.wait(rows.map(userPhoneNumbers.mapFromRow))); + } + + i0.Selectable getUserPhoneNumbers({required String userId}) { + return customSelect('SELECT * FROM user_phone_numbers WHERE user_id = ?1', + variables: [ + i0.Variable(userId) + ], + readsFrom: { + userPhoneNumbers, + }).asyncMap(userPhoneNumbers.mapFromRow); + } + + i0.Selectable lookupUserByPhone( + {required String phoneNumber}) { + return customSelect( + 'SELECT"users"."user_id" AS "nested_0.user_id", "users"."given_name" AS "nested_0.given_name", "users"."family_name" AS "nested_0.family_name", "users"."time_zone" AS "nested_0.time_zone", "users"."language_code" AS "nested_0.language_code", "users"."create_time" AS "nested_0.create_time", "users"."update_time" AS "nested_0.update_time","user_phone_numbers"."user_id" AS "nested_1.user_id", "user_phone_numbers"."phone_number" AS "nested_1.phone_number", "user_phone_numbers"."is_verified" AS "nested_1.is_verified", "user_phone_numbers"."is_primary" AS "nested_1.is_primary" FROM users INNER JOIN user_phone_numbers ON users.user_id = user_phone_numbers.user_id WHERE user_phone_numbers.phone_number = ?1 AND user_phone_numbers.is_verified ORDER BY user_phone_numbers.is_primary DESC', + variables: [ + i0.Variable(phoneNumber) + ], + readsFrom: { + users, + userPhoneNumbers, + }).asyncMap((i0.QueryRow row) async => LookupUserByPhoneResult( + users: await users.mapFromRow(row, tablePrefix: 'nested_0'), + userPhoneNumbers: + await userPhoneNumbers.mapFromRow(row, tablePrefix: 'nested_1'), + )); + } + + Future deleteUserPhoneNumber( + {required String userId, required String phoneNumber}) { + return customUpdate( + 'DELETE FROM user_phone_numbers WHERE user_id = ?1 AND phone_number = ?2', + variables: [ + i0.Variable(userId), + i0.Variable(phoneNumber) + ], + updates: {userPhoneNumbers}, + updateKind: i0.UpdateKind.delete, + ); + } + + i2.Users get users => + i4.ReadDatabaseContainer(attachedDatabase).resultSet('users'); + i2.UserEmails get userEmails => i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('user_emails'); + i2.UserPhoneNumbers get userPhoneNumbers => + i4.ReadDatabaseContainer(attachedDatabase) + .resultSet('user_phone_numbers'); +} + +class LookupUserByEmailResult { + final i1.User users; + final i1.Email userEmails; + LookupUserByEmailResult({ + required this.users, + required this.userEmails, + }); +} + +class LookupUserByPhoneResult { + final i1.User users; + final i1.PhoneNumber userPhoneNumbers; + LookupUserByPhoneResult({ + required this.users, + required this.userPhoneNumbers, + }); +} diff --git a/services/celest_cloud_auth/lib/src/database/schema_versions.dart b/services/celest_cloud_auth/lib/src/database/schema_versions.dart new file mode 100644 index 00000000..200f551a --- /dev/null +++ b/services/celest_cloud_auth/lib/src/database/schema_versions.dart @@ -0,0 +1,16 @@ +import 'package:drift/internal/versioned_schema.dart' as i0; +import 'package:drift/drift.dart' as i1; +import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import + +// GENERATED BY drift_dev, DO NOT MODIFY. +i0.MigrationStepWithVersion migrationSteps() { + return (currentVersion, database) async { + switch (currentVersion) { + default: + throw ArgumentError.value('Unknown migration from $currentVersion'); + } + }; +} + +i1.OnUpgrade stepByStep() => + i0.VersionedSchema.stepByStepHelper(step: migrationSteps()); diff --git a/services/celest_cloud_auth/lib/src/email/email_model.dart b/services/celest_cloud_auth/lib/src/email/email_model.dart new file mode 100644 index 00000000..42998d15 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/email/email_model.dart @@ -0,0 +1,107 @@ +import 'package:celest_cloud_auth/src/email/templates/verification_code.dart'; + +sealed class EmailBody { + const factory EmailBody.text(String text) = EmailBodyText; + const factory EmailBody.html(String html) = EmailBodyHtml; + + const EmailBody._(this.content); + + final String content; + + @override + String toString() => content; +} + +class EmailBodyText extends EmailBody { + const EmailBodyText(super.text) : super._(); +} + +class EmailBodyHtml extends EmailBody { + const EmailBodyHtml(super.html) : super._(); +} + +abstract interface class EmailTemplate { + static const VerificationCodeEmail verificationCode = VerificationCodeEmail(); + + String render(Params params); +} + +/// {@template celest_cloud_auth.email.email_message} +/// Email message to be sent. +/// {@endtemplate} +final class EmailMessage { + /// {@macro celest_cloud_auth.email.email_message} + const EmailMessage({ + required this.from, + required this.to, + required this.subject, + required this.body, + this.bcc, + this.cc, + this.replyTo, + this.headers, + this.scheduledAt, + }); + + /// Sender email address. + /// + /// To include a friendly name, use the format `Your Name `. + final String from; + + /// Recipient email address. + /// + /// A maximum of 50 recipients can be specified. + final List to; + + /// Email subject. + final String subject; + + /// Bcc recipient email addresses. + final List? bcc; + + /// Cc recipient email addresses. + final List? cc; + + /// Reply-to email addresses. + final List? replyTo; + + /// The body of the message. + /// + /// Either [EmailBodyText] or [EmailBodyHtml] can be used. + final EmailBody body; + + /// Custom headers to add to the email. + final Map? headers; + + /// Schedules the email to be sent at a later time. + final DateTime? scheduledAt; + + @override + String toString() { + final buf = StringBuffer('Email('); + buf.write('from: $from, '); + buf.write('to: $to, '); + buf.write('subject: $subject'); + if (bcc != null) { + buf.write(', bcc: $bcc'); + } + if (cc != null) { + buf.write(', cc: $cc'); + } + if (replyTo != null) { + buf.write(', replyTo: $replyTo'); + } + buf.write(')'); + if (scheduledAt case final scheduledAt?) { + buf.write(' scheduledAt: $scheduledAt'); + } + buf.writeln(); + if (headers case final headers?) { + for (final entry in headers.entries) { + buf.writeln('${entry.key}: ${entry.value}'); + } + } + buf.writeln(body); + return buf.toString(); + } +} diff --git a/services/celest_cloud_auth/lib/src/email/email_provider.dart b/services/celest_cloud_auth/lib/src/email/email_provider.dart new file mode 100644 index 00000000..ffb58489 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/email/email_provider.dart @@ -0,0 +1,17 @@ +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/otp/otp_provider.dart'; + +/// A provider for sending OTP codes via email. +extension type const EmailOtpProvider._(OtpSender _send) implements Object { + /// Creates a new [EmailOtpProvider] with the given [send] function. + const EmailOtpProvider([OtpSender send = _defaultSend]) : this._(send); + + static Future _defaultSend(Otp message) async { + context.logger.info('Verification code for ${message.to}: ${message.code}'); + } + + /// Sends an email [message] and returns the message ID. + Future send(Otp message) { + return _send(message); + } +} diff --git a/services/celest_cloud_auth/lib/src/email/resend_email_provider.dart b/services/celest_cloud_auth/lib/src/email/resend_email_provider.dart new file mode 100644 index 00000000..8b9d62d3 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/email/resend_email_provider.dart @@ -0,0 +1,99 @@ +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/email/email_model.dart'; +import 'package:celest_cloud_auth/src/email/email_provider.dart'; +import 'package:celest_cloud_auth/src/email/templates/verification_code.dart'; +import 'package:celest_cloud_auth/src/otp/otp_provider.dart'; +import 'package:celest_core/_internal.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:http/http.dart' as http; + +/// {@template celest_cloud_auth.email.resend_api} +/// An API client for the [Resend](https://resend.com/) email service. +/// {@endtemplate} +extension type ResendEmailProvider._(OtpSender _send) + implements EmailOtpProvider { + /// Creates a new [ResendEmailProvider] client. + /// + /// {@macro celest_cloud_auth.email.resend_api} + ResendEmailProvider({ + required String apiKey, + required http.Client client, + Uri? baseUri, + }) : this._( + (Otp otp) async { + final (:to, :code) = otp; + baseUri ??= Uri.parse('https://api.resend.com'); + final uri = baseUri!.resolve('./emails'); + final message = EmailMessage( + from: 'no-reply@auth.celest.dev', + to: [to], + subject: 'Verify your account', + body: EmailBody.html( + EmailTemplate.verificationCode.render( + VerificationCodeEmailParams( + email: to, + code: code, + organizationName: 'Celest', + type: VerificationCodeEmailType.generic, + ), + ), + ), + ); + final res = await client.post( + uri, + body: JsonUtf8.encode(SendEmailRequest(message).toJson()), + headers: { + 'content-type': 'application/json', + 'authorization': 'Bearer $apiKey', + }, + ); + if (res.statusCode != HttpStatus.ok) { + throw CloudException.fromHttpResponse(res); + } + final response = SendEmailResponse( + JsonUtf8.decodeMap(res.bodyBytes), + ); + context.logger.info('Email sent to $to: ${response.id}'); + }, + ); +} + +/// A request to send an email via the Resend API.s +extension type SendEmailRequest(EmailMessage email) { + /// Converts the email message to a JSON object. + /// + /// https://raw.githubusercontent.com/resend/resend-openapi/main/resend.yaml + Map toJson() => { + 'from': email.from, + 'to': email.to, + 'subject': email.subject, + if (email.bcc != null) + 'bcc': switch (email.bcc) { + [final single] => single, + final multiple => multiple, + }, + if (email.cc != null) + 'cc': switch (email.cc) { + [final single] => single, + final multiple => multiple, + }, + ...switch (email.body) { + final EmailBodyText text => {'text': text.content}, + final EmailBodyHtml html => {'html': html.content}, + }, + if (email.replyTo != null) + 'reply_to': switch (email.replyTo) { + [final single] => single, + final multiple => multiple, + }, + if (email.headers case final headers?) 'headers': headers, + if (email.scheduledAt case final scheduledAt?) + 'scheduled_at': scheduledAt.toIso8601String(), + }; +} + +/// A response from the Resend API after sending an email. +extension type SendEmailResponse(Map _json) { + /// The ID of the sent email. + String get id => _json['id'] as String? ?? ''; +} diff --git a/services/celest_cloud_auth/lib/src/email/templates/VerificationCode.html b/services/celest_cloud_auth/lib/src/email/templates/VerificationCode.html new file mode 100644 index 00000000..cbe8ab36 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/email/templates/VerificationCode.html @@ -0,0 +1,30 @@ +
Your {{ type }} code for {{ organizationName }} is {{ code }}
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/services/celest_cloud_auth/lib/src/email/templates/verification_code.dart b/services/celest_cloud_auth/lib/src/email/templates/verification_code.dart new file mode 100644 index 00000000..fc23365e --- /dev/null +++ b/services/celest_cloud_auth/lib/src/email/templates/verification_code.dart @@ -0,0 +1,96 @@ +import 'package:celest_cloud_auth/src/email/email_model.dart'; +import 'package:mustache_template/mustache.dart'; + +enum VerificationCodeEmailType { + login, + register, + generic; + + String get type => switch (this) { + login => 'login', + register => 'registration', + generic => 'verification', + }; + + String get purpose => switch (this) { + login => 'log in to', + register => 'create an account with', + generic => 'verify this email address with', + }; +} + +final class VerificationCodeEmailParams { + const VerificationCodeEmailParams({ + this.type = VerificationCodeEmailType.generic, + required this.email, + required this.code, + required this.organizationName, + this.name, + this.logoUrl, + }); + + final VerificationCodeEmailType type; + final String email; + final String code; + final String organizationName; + final String? name; + final String? logoUrl; + + Map toTemplateParams() => { + 'email': email, + 'code': code, + 'organizationName': organizationName, + if (name case final name?) 'name': name, + if (logoUrl case final logoUrl?) 'logoUrl': logoUrl, + 'type': type.type, + 'purpose': type.purpose, + }; +} + +final class VerificationCodeEmail + implements EmailTemplate { + const VerificationCodeEmail(); + + static final Template _template = Template( + _templateHtml, + htmlEscapeValues: true, + ); + + @override + String render(VerificationCodeEmailParams params) { + return _template.renderString(params.toTemplateParams()); + } +} + +const _templateHtml = r''' +
Your {{ type }} code for {{ organizationName }} is {{ code }}
+'''; diff --git a/services/celest_cloud_auth/lib/src/http/cookie_cork.dart b/services/celest_cloud_auth/lib/src/http/cookie_cork.dart new file mode 100644 index 00000000..c90e3725 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/http/cookie_cork.dart @@ -0,0 +1,23 @@ +import 'dart:io'; + +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:clock/clock.dart'; +import 'package:corks_cedar/corks_cedar.dart'; + +/// A wrapper over a [Cookie] for corks. +extension type Corkie._(Cookie cookie) implements Cookie { + Corkie.set(Cork cork) : this._create(cork.toString()); + + Corkie.clear() : this._create(''); + + Corkie._create(String cork) + : cookie = Cookie(cookieName, cork) + ..domain = context.hostname + ..httpOnly = true + ..secure = context.isRunningInCloud + ..path = '/' + ..sameSite = SameSite.lax + ..expires = clock.now().add(const Duration(days: 30)); + + static const String cookieName = 'cork'; +} diff --git a/services/celest_cloud_auth/lib/src/http/http_helpers.dart b/services/celest_cloud_auth/lib/src/http/http_helpers.dart new file mode 100644 index 00000000..552bc5e2 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/http/http_helpers.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:celest_cloud_auth/src/http/cookie_cork.dart'; +import 'package:celest_cloud_auth/src/model/cookie.dart'; +import 'package:celest_core/_internal.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:protobuf/protobuf.dart'; +import 'package:shelf/shelf.dart'; + +extension RequestHelpers on Request { + static final Expando> _cookiesExpando = Expando(); + + /// Returns the cookies sent with this request. + Map get cookies => + _cookiesExpando[this] ??= parseCookies(headersAll); + + /// Returns the IP address of the client who initiated this request. + String get clientIp { + final connectionInfo = + context['shelf.io.connection_info'] as HttpConnectionInfo; + return switch (headers['x-forwarded-for']?.split(',').firstOrNull?.trim()) { + final ip? when ip.isNotEmpty => ip, + _ => connectionInfo.remoteAddress.address, + }; + } +} + +extension ResponseHelpers on Response { + /// Adds a `Set-Cookie` header with the given [cookie]. + Response setCookie(Cookie cookie) { + return change( + headers: { + ...headers, + HttpHeaders.setCookieHeader: [ + ...?headersAll[HttpHeaders.setCookieHeader], + cookie.toString(), + ], + }, + ); + } + + /// Adds a `Set-Cookie` header with the given [cork]. + Response setCork(Cork cork) { + return setCookie(Corkie.set(cork)); + } + + /// Adds a `Set-Cookie` header to clear the user's cork. + Response clearCookie() { + return setCookie(Corkie.clear()); + } +} + +extension GeneratedMessageHelpers on GeneratedMessage { + Response jsonResponse() { + return Response.ok( + JsonUtf8.encode(toProto3Json()), + headers: { + 'content-type': 'application/json', + }, + ); + } +} diff --git a/services/celest_cloud_auth/lib/src/model/cookie.dart b/services/celest_cloud_auth/lib/src/model/cookie.dart new file mode 100644 index 00000000..b2c1f903 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/cookie.dart @@ -0,0 +1,64 @@ +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:petitparser/petitparser.dart'; + +/// Parses a cookie header into a map of name/value pairs. +Map parseCookies(Map> headers) { + final cookieHeader = headers['cookie']; + if (cookieHeader == null || cookieHeader.isEmpty) { + return const {}; + } + + final cookies = {}; + for (final line in cookieHeader) { + if (line.isEmpty) { + continue; + } + + // Parse each cookie individually so that we can handle parser errors more + // gracefully. + final cookiePairs = line.trim().split(';'); + for (var cookiePair in cookiePairs) { + cookiePair = cookiePair.trim(); + if (cookiePair.isEmpty) { + continue; + } + switch (_cookieParser.parse(cookiePair)) { + case Success(value: (final name, final value)): + cookies[name] = value; + case final Failure failure: + context.logger.warning( + 'Failed to parse cookie: ${failure.message}', + FormatException(failure.message, cookiePair, failure.position), + ); + } + } + } + + return Map.unmodifiable(cookies); +} + +// Follows the cookie grammar as defined in RFC 6265 +// https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1 +final Parser<(String, String)> _cookieParser = () { + // token = + // https://datatracker.ietf.org/doc/html/rfc2616#section-2.2 + final asciiNoSeparators = pattern('^()<>@,;:\\"/[]?={} \t'); + final token = asciiNoSeparators.plus().flatten(); + + // cookie-name = token + final cookieName = token; + + // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + final cookieOctet = pattern('\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E'); + + // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + final dquote = char('"').optional(); + final cookieValue = + cookieOctet.star().flatten().skip(before: dquote, after: dquote); + + // cookie-pair = cookie-name "=" cookie-value + final cookiePair = seq2(cookieName, cookieValue.skip(before: char('='))); + + // cookie-header = "Cookie:" OWS cookie-string OWS + return cookiePair.trim().end(); +}(); diff --git a/services/celest_cloud_auth/lib/src/model/interop.dart b/services/celest_cloud_auth/lib/src/model/interop.dart new file mode 100644 index 00000000..06ab72b7 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/interop.dart @@ -0,0 +1,75 @@ +import 'package:celest_cloud/src/proto.dart' as pb; +import 'package:celest_core/celest_core.dart'; + +extension UserInterop on User { + pb.User toProto() { + return pb.User( + name: 'users/$userId', + userId: userId, + familyName: familyName, + givenName: givenName, + phoneNumbers: phoneNumbers.map((pn) => pn.toProto()), + emails: emails.map((e) => e.toProto()), + languageCode: languageCode, + timeZone: timeZone, + createTime: createTime?.toProto(), + updateTime: updateTime?.toProto(), + ); + } +} + +extension UserProtoInterop on pb.User { + User toModel() { + return User( + userId: userId, + familyName: hasFamilyName() ? familyName : null, + givenName: hasGivenName() ? givenName : null, + phoneNumbers: phoneNumbers.map((pn) => pn.toModel()).toList(), + emails: emails.map((e) => e.toModel()).toList(), + languageCode: hasLanguageCode() ? languageCode : null, + timeZone: hasTimeZone() ? timeZone : null, + createTime: hasCreateTime() ? createTime.toDateTime() : null, + updateTime: hasUpdateTime() ? updateTime.toDateTime() : null, + ); + } +} + +extension EmailInterop on Email { + pb.Email toProto() { + return pb.Email( + email: email, + isVerified: isVerified, + isPrimary: isPrimary, + ); + } +} + +extension EmailProtoInterop on pb.Email { + Email toModel() { + return Email( + email: email, + isVerified: isVerified, + isPrimary: isPrimary, + ); + } +} + +extension PhoneNumberInterop on PhoneNumber { + pb.PhoneNumber toProto() { + return pb.PhoneNumber( + phoneNumber: phoneNumber, + isVerified: isVerified, + isPrimary: isPrimary, + ); + } +} + +extension PhoneNumberProtoInterop on pb.PhoneNumber { + PhoneNumber toModel() { + return PhoneNumber( + phoneNumber: phoneNumber, + isVerified: isVerified, + isPrimary: isPrimary, + ); + } +} diff --git a/services/celest_cloud_auth/lib/src/model/order_by.dart b/services/celest_cloud_auth/lib/src/model/order_by.dart new file mode 100644 index 00000000..a9268057 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/order_by.dart @@ -0,0 +1,109 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:celest_core/celest_core.dart'; +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:petitparser/petitparser.dart'; + +final class OrderByField { + const OrderByField( + this.path, { + this.mode = OrderingMode.asc, + }); + + final List path; + final OrderingMode mode; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is OrderByField && + runtimeType == other.runtimeType && + path.equals(other.path) && + mode == other.mode; + } + + @override + int get hashCode => Object.hashAll([mode, ...path]); + + @override + String toString() { + return '${path.join('.')} ${mode.name.toUpperCase()}'; + } +} + +extension type const OrderByClause(List fields) + implements List { + factory OrderByClause.parse(String orderBy) { + final result = switch (_parser.parse(orderBy)) { + Success(:final value) => value, + final Failure failure => throw FormatException( + failure.message, + orderBy, + failure.position, + ), + }; + return OrderByClause(result); + } + + Iterable + toOrderingTerms>( + Tbl table, + ) sync* { + for (final field in fields) { + if (field.path.length > 1) { + throw BadRequestException( + 'Cannot order by "${field.path.join('.')}": ' + 'Currently only single field ordering is supported.', + ); + } + final orderByCol = table.columnsByName[field.path.first]; + if (orderByCol == null) { + throw BadRequestException( + 'Cannot order by "${field.path.join('.')}": ' + 'Column not found.', + ); + } + yield OrderingTerm(expression: orderByCol, mode: field.mode); + } + } +} + +/// Implements a subset of the orderBy spec used by GCP. +/// +/// https://cloud.google.com/monitoring/api/v3/sorting-and-filtering#sort-order_syntax +final Parser> _parser = () { + // IDENTIFIER + final Parser identifier = + (letter() & word().star()).flatten('IDENTIFIER'); + + // FIELD_NAME + final fieldName = [ + identifier.plusSeparated(char('.')).map((el) => el.elements), + identifier.map((it) => [it]), + ].toChoiceParser().labeled('FIELD_NAME'); + + // FIELD_REFERENCE + final fieldReference = fieldName.labeled('FIELD_REFERENCE'); + + // ORDERED_FIELD + final ascendingField = + fieldReference.map((name) => OrderByField(name, mode: OrderingMode.asc)); + final descendingField = fieldReference + .skip(before: char('-')) + .map((name) => OrderByField(name, mode: OrderingMode.desc)); + final orderedField = [descendingField, ascendingField] + .toChoiceParser() + .labeled('ORDERED_FIELD'); + + // ORDERED_FIELD_LIST + final orderedFieldList = orderedField + .plusSeparated(char(',').trim()) + .labeled('ORDERED_FIELD_LIST'); + + // ORDER_BY_SPEC + return orderedFieldList + .map((res) => res.elements) + .end() + .labeled('ORDER_BY_SPEC'); +}(); diff --git a/services/celest_cloud_auth/lib/src/model/page_token.dart b/services/celest_cloud_auth/lib/src/model/page_token.dart new file mode 100644 index 00000000..8151617d --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/page_token.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +import 'package:celest_cloud/src/proto.dart'; +import 'package:celest_cloud/src/proto/celest/cloud/v1alpha1/common.pb.dart' + as pb; +import 'package:fixnum/fixnum.dart'; + +extension type const PageToken._(pb.PageToken _pb) { + factory PageToken({ + int offset = 0, + DateTime? startTime, + bool? showDeleted, + }) { + return PageToken._( + pb.PageToken( + offset: Int64(offset), + startTime: startTime?.toProto(), + showDeleted: showDeleted, + ), + ); + } + + factory PageToken.parse(String token) { + if (token.isEmpty) { + return PageToken._(pb.PageToken()); + } + final decoded = base64Decode(token); + final pbToken = pb.PageToken.fromBuffer(decoded); + return PageToken._(pbToken); + } + + int get offset => _pb.offset.toInt(); + DateTime? get startTime => + _pb.hasStartTime() ? _pb.startTime.toDateTime() : null; + bool get showDeleted => _pb.showDeleted; + + String encode() => base64Encode(_pb.writeToBuffer()); +} diff --git a/services/celest_cloud_auth/lib/src/model/route.dart b/services/celest_cloud_auth/lib/src/model/route.dart new file mode 100644 index 00000000..eea57bea --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/route.dart @@ -0,0 +1,338 @@ +import 'package:celest_cloud_auth/src/context.dart' show context; +import 'package:collection/collection.dart'; +import 'package:petitparser/debug.dart'; +import 'package:petitparser/petitparser.dart'; + +/// {@template celest_cloud_auth.model.route} +/// A route is a path template that is used for routing HTTP requests and for +/// linking them back to the appropriate cloud function definition. +/// +/// This is especially important in Auth since authorization happens on a +/// per-function and per-API basis. +/// {@endtemplate} +final class Route extends Parser> { + /// {@macro celest_cloud_auth.model.route} + Route({ + required this.template, + required this.segments, + this.verb, + }); + + /// Parses a path [template] into a [Route]. + /// + /// {@macro celest_cloud_auth.model.route} + factory Route.parse(String template) { + final parsed = _segmentParser.parse(template); + switch (parsed) { + case Success(value: (final segments, final verb)): + return Route(template: template, segments: segments, verb: verb); + case Failure(:final message, :final position): + throw FormatException(message, template, position); + } + } + + /// Folows the path template syntax defined [here](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L220). + static final Parser<(List, RouteVerb?)> _segmentParser = () { + final ident = word().plus().flatten(); + + // FieldPath = IDENT { "." IDENT } ; + final fieldPath = ident.plusSeparated(char('.')); + + final segment = undefined(); + + // Segments = Segment { "/" Segment } ; + final segments = segment.plusSeparated(char('/')); + + // Variable = "{" FieldPath [ "=" Segments ] "}" ; + final variable = seq2( + fieldPath.flatten().skip(after: char('=')), + segments, + ) + .map2( + (variable, segments) => RouteParameter( + variable: variable, + segments: segments.elements, + ), + ) + .skip(before: char('{'), after: char('}')); + + final literal = (word() | char('-')).plus().flatten(); + + // Segment = "*" | "**" | LITERAL | Variable ; + segment.set( + [ + string('**').map((_) => RouteWildcard(greedy: true)), + char('*').map((_) => RouteWildcard(greedy: false)), + variable, + literal.map(RouteLiteral.new), + ].toChoiceParser(), + ); + + // Verb = ":" LITERAL ; + final verb = + word().plus().flatten().map(RouteVerb.new).skip(before: char(':')); + + // Template = "/" Segments [ Verb ] ; + final parser = + seq2(segments, verb.optional()).skip(before: char('/')).end(); + + return parser.map((result) => (result.$1.elements, result.$2)); + }(); + + /// The template used to create this route. + final String template; + + /// The segments of this route. + /// + /// A segment is either a [RouteLiteral], a [RouteWildcard], or a + /// [RouteParameter]. + /// + /// They are matched sequentially against the path segments of a request. + final List segments; + + /// The verb of this route. + final RouteVerb? verb; + + late final Parser _parser = segments + .map((segment) => segment.skip(before: char('/'))) + .toSequenceParser() + .seq(verb ?? epsilon()) + .end(); + + /// Returns a map of variables to values captured from the [path] if it + /// matches this route. + Map? match(String path, {bool debug = false}) { + Parser> parser = this; + if (debug) { + parser = trace(profile(parser)); + } + + final result = parser.parse(path); + switch (result) { + case Success(:final value): + return value; + case Failure(:final message, :final position): + final error = FormatException(message, path, position); + context.logger.finest( + 'Failed to parse route "$path" (pattern=$this)', + error, + ); + return null; + } + } + + /// Returns `true` if the [path] matches this route. + bool matches(String path) { + return match(path) != null; + } + + @override + Result> parseOn(Context context) { + final result = _parser.parseOn(context); + if (result is Failure) { + return result; + } + return result.success({ + for (final segment in segments.whereType()) + segment.variable: segment._value, + }); + } + + @override + List> get children => segments; + + @override + Parser> copy() { + return Route( + template: template, + segments: segments, + ); + } + + @override + String toString() => '/${segments.join('/')}${verb ?? ''}'; +} + +/// A segment of a route. +sealed class RouteSegment extends Parser {} + +/// {@template celest_cloud_auth.model.route_literal} +/// A literal segment of a route. +/// {@endtemplate} +final class RouteLiteral extends RouteSegment { + /// {@macro celest_cloud_auth.model.route_literal} + RouteLiteral(this.literal); + + /// The literal segment. + final String literal; + + @override + Result parseOn(Context context) { + return string(literal).parseOn(context); + } + + @override + RouteLiteral copy() { + return RouteLiteral(literal); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is RouteLiteral && literal == other.literal; + } + + @override + int get hashCode => literal.hashCode; + + @override + String toString() => literal; +} + +/// {@template celest_cloud_auth.model.route_wildcard} +/// A wildcard segment of a route. +/// +/// If [greedy] is `true`, the wildcard will match zero or multiple path segments +/// and must be the last segment of the route. +/// +/// Otherwise, it will match a single path segment. +/// {@endtemplate} +final class RouteWildcard extends RouteSegment { + /// {@macro celest_cloud_auth.model.route_wildcard} + RouteWildcard({ + required this.greedy, + }); + + /// Whether the wildcard consumes all remaining path segments. + final bool greedy; + + /// The maximum length of a URL. + /// + /// Not a standard, but a generally accepted upper bound on reasonable URLs. + static const int _maxUrlLength = 2048; + + late final Parser _parser = greedy + // The syntax `**` matches zero or more URL path segments + ? (word() | char('/')) + .repeatLazy(endOfInput(), 0, _maxUrlLength) + .flatten('**') + + // The syntax `*` matches a single URL path segment. + : word().plus().flatten('*'); + + @override + Result parseOn(Context context) { + return _parser.parseOn(context); + } + + @override + RouteWildcard copy() { + return RouteWildcard(greedy: greedy); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is RouteWildcard && greedy == other.greedy; + } + + @override + int get hashCode => greedy.hashCode; + + @override + String toString() => greedy ? '**' : '*'; +} + +/// {@template celest_cloud_auth.model.route_parameter} +/// A parameter of a route which binds a [variable] to a value which is the +/// concatenation of one or more [segments]. +/// {@endtemplate} +final class RouteParameter extends RouteSegment { + /// {@macro celest_cloud_auth.model.route_parameter} + RouteParameter({ + required this.variable, + required this.segments, + }); + + /// The variable name of this parameter. + final String variable; + + /// The sub-segments of this parameter. + final List segments; + + /// The value which is computed upon parsing. + late String _value; + + late final Parser _parser = segments + .expandIndexed((index, segment) { + if (index == 0) { + return [segment]; + } + return [char('/'), segment]; + }) + .toSequenceParser() + .flatten(segments.join('/')) + .map((value) => _value = value); + + @override + Result parseOn(Context context) { + return _parser.parseOn(context); + } + + @override + RouteParameter copy() { + return RouteParameter( + variable: variable, + segments: segments, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is RouteParameter && + variable == other.variable && + segments.equals(other.segments); + } + + @override + int get hashCode => Object.hashAll([variable, ...segments]); + + @override + String toString() => '{$variable=${segments.join('/')}}'; +} + +/// {@template celest_cloud_auth.model.route_verb} +/// A verb literal which is optionally appended to a route. +/// {@endtemplate} +final class RouteVerb extends RouteSegment { + /// {@macro celest_cloud_auth.model.route_verb} + RouteVerb(this.verb); + + /// The literal verb. + final String verb; + + late final _parser = + [char(':'), string(verb)].toSequenceParser().flatten(':$verb'); + + @override + Result parseOn(Context context) { + return _parser.parseOn(context); + } + + @override + RouteVerb copy() { + return RouteVerb(verb); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || other is RouteVerb && verb == other.verb; + } + + @override + int get hashCode => verb.hashCode; + + @override + String toString() => ':$verb'; +} diff --git a/services/celest_cloud_auth/lib/src/model/route_map.dart b/services/celest_cloud_auth/lib/src/model/route_map.dart new file mode 100644 index 00000000..822a8115 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/model/route_map.dart @@ -0,0 +1,65 @@ +import 'package:cedar/cedar.dart'; +import 'package:celest_ast/celest_ast.dart'; +import 'package:celest_cloud_auth/src/authorization/cedar_interop.dart'; +import 'package:celest_cloud_auth/src/model/route.dart'; + +/// A map of routes to their respective [EntityUid]s. +extension type const RouteMap(Map _routes) + implements Map { + /// Computes the route map for the given [project]. + /// + /// Optionally, [additionalRoutes] can be provided to be included in the map. + factory RouteMap.of( + ResolvedProject project, { + RouteMap additionalRoutes = const RouteMap({}), + }) { + final collector = _RouteCollector(); + project.accept(collector); + final routeMap = RouteMap({ + ...collector._routes, + ...additionalRoutes, + }); + print(routeMap); + return routeMap; + } +} + +final class _RouteCollector extends ResolvedAstVisitor { + final Map _routes = {}; + + @override + void visitProject(ResolvedProject project) { + project.apis.values.forEach(visitApi); + } + + @override + void visitApi(ResolvedApi api) { + api.functions.values.forEach(visitFunction); + } + + @override + void visitAuth(ResolvedAuth auth) {} + + @override + void visitAuthProvider(ResolvedAuthProvider provider) {} + + @override + void visitVariable(ResolvedVariable variable) {} + + @override + void visitFunction(ResolvedCloudFunction function) { + _routes[function.uid] = Route.parse(function.httpConfig.route.path); + } + + @override + void visitDatabase(ResolvedDatabase database) {} + + @override + void visitDatabaseSchema(ResolvedDatabaseSchema schema) {} + + @override + void visitExternalAuthProvider(ResolvedExternalAuthProvider provider) {} + + @override + void visitSecret(ResolvedSecret secret) {} +} diff --git a/services/celest_cloud_auth/lib/src/otp/otp_provider.dart b/services/celest_cloud_auth/lib/src/otp/otp_provider.dart new file mode 100644 index 00000000..77557e00 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/otp/otp_provider.dart @@ -0,0 +1,6 @@ +typedef Otp = ({ + String to, + String code, +}); + +typedef OtpSender = Future Function(Otp); diff --git a/services/celest_cloud_auth/lib/src/otp/otp_repository.dart b/services/celest_cloud_auth/lib/src/otp/otp_repository.dart new file mode 100644 index 00000000..484da5d9 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/otp/otp_repository.dart @@ -0,0 +1,185 @@ +import 'dart:typed_data'; + +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_repository.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/database/schema/auth.drift.dart'; +import 'package:celest_cloud_auth/src/email/email_provider.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:clock/clock.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:meta/meta.dart'; + +typedef OtpData = ({ + OtpCode data, + String code, +}); + +typedef _Deps = ({ + CryptoKeyRepository cryptoKeys, + AuthDatabase db, +}); + +/// A provider for generating and verifying OTP codes. +extension type OtpRepository._(_Deps _deps) implements Object { + OtpRepository({ + required CryptoKeyRepository cryptoKeys, + required AuthDatabase db, + }) : this._( + ( + cryptoKeys: cryptoKeys, + db: db, + ), + ); + + CryptoKeyRepository get _cryptoKeys => _deps.cryptoKeys; + AuthDatabase get _db => _deps.db; + + /// The rate limiting options for resending of an OTP code for a single + /// session. + static const Duration resendDelay = Duration(minutes: 1); + static const int maxResendAttempts = 3; + + /// The rate limiting options for verification of an OTP code for a single + /// session. + static const Duration verifyDelay = Duration(seconds: 10); + static const int maxVerifyAttempts = 3; + + Future<(bool, DateTime?)> send({ + required TypeId sessionId, + required String to, + required EmailOtpProvider provider, + }) async { + final data = + (await _db.authDrift.upsertOtpCode(sessionId: sessionId.encoded)).first; + if (canResend(data) case (false, final nextResend)) { + return (false, nextResend); + } + final session = await _db.authDrift + .getSession(sessionId: sessionId.encoded) + .getSingle(); + final key = await _cryptoKeys.getKey(cryptoKeyId: session.cryptoKeyId); + final code = await generate( + counter: data.rowid, + signer: key.signer, + ); + await provider.send((to: to, code: code)); + context.logger.fine('Sent OTP code to $to: $code'); + return (true, await _incrementResend(data)); + } + + /// Verifies an OTP code. + Future<(bool, DateTime?)> verify({ + required TypeId sessionId, + required String code, + }) async { + final data = await _db.authDrift + .getOtpCode(sessionId: sessionId.encoded) + .getSingle(); + if (canVerify(data) case (false, final nextVerify)) { + context.logger.warning('Cannot verify OTP code again until $nextVerify'); + return (false, nextVerify); + } + final session = await _db.authDrift + .getSession(sessionId: sessionId.encoded) + .getSingle(); + final key = await _cryptoKeys.getKey(cryptoKeyId: session.cryptoKeyId); + final expected = await generate( + counter: data.rowid, + signer: key.signer, + ); + if (expected != code) { + context.logger.warning('Invalid OTP code: $code'); + return (false, await _incrementVerify(data)); + } + context.logger.fine('Verified OTP code'); + return (true, null); + } + + /// Generates a 6-digit [OtpCode] as specified in [RFC 4226](https://tools.ietf.org/html/rfc4226). + @visibleForTesting + Future generate({ + required int counter, + required Signer signer, + }) async { + final buf = Uint8List(8); + buf.buffer.asByteData().setUint64(0, counter, Endian.big); + + context.logger.finest('HOTP counter: $counter'); + + final sum = await signer.sign(buf); + + // Dynamic truncation per RFC 4226. + // http://tools.ietf.org/html/rfc4226#section-5.4 + final offset = sum.last & 0x0F; + var code = + sum.buffer.asByteData().getUint32(offset, Endian.big) & 0x7FFFFFFF; + + // Generate a six-digit code. + code %= 1000000; + final codeUnits = Uint8List(6); + for (var i = 5; i >= 0; i--) { + const asciiZero = 0x30; + codeUnits[i] = asciiZero + code % 10; + code ~/= 10; + } + + return String.fromCharCodes(codeUnits); + } + + Future _incrementResend(OtpCode otpCode) async { + final data = await _db.authDrift.updateOtpCode( + sessionId: otpCode.sessionId, + resendAttempt: otpCode.resendAttempt + 1, + ); + return nextResend(data.first); + } + + Future _incrementVerify(OtpCode otpCode) async { + final data = await _db.authDrift.updateOtpCode( + sessionId: otpCode.sessionId, + verifyAttempt: otpCode.verifyAttempt + 1, + ); + return nextVerify(data.first); + } + + DateTime nextResend(OtpCode otpCode) { + return otpCode.updateTime.add(resendDelay); + } + + (bool, DateTime?) canResend(OtpCode otpCode) { + if (otpCode.resendAttempt == 0) { + return (true, nextResend(otpCode)); + } + if (otpCode.resendAttempt > maxResendAttempts) { + return (false, null); + } + final expectedResendAt = otpCode.updateTime.add(resendDelay); + if (clock.now().isBefore(expectedResendAt)) { + return (false, expectedResendAt); + } + return (true, null); + } + + DateTime nextVerify(OtpCode otpCode) { + return otpCode.updateTime.add(verifyDelay); + } + + (bool, DateTime?) canVerify(OtpCode otpCode) { + if (otpCode.verifyAttempt == 0) { + return (true, nextVerify(otpCode)); + } + if (otpCode.verifyAttempt >= maxVerifyAttempts) { + return (false, null); + } + if (otpCode.verifyAttempt == 0) { + return (true, null); + } + final expectedVerifyAt = otpCode.updateTime.add(verifyDelay); + if (clock.now().isBefore(expectedVerifyAt)) { + return (false, expectedVerifyAt); + } + return (true, null); + } +} diff --git a/services/celest_cloud_auth/lib/src/users/users_service.dart b/services/celest_cloud_auth/lib/src/users/users_service.dart new file mode 100644 index 00000000..11f70c05 --- /dev/null +++ b/services/celest_cloud_auth/lib/src/users/users_service.dart @@ -0,0 +1,387 @@ +import 'package:cedar/ast.dart' hide Value; +import 'package:cedar/cedar.dart' hide Value; +// ignore: invalid_use_of_internal_member +import 'package:celest/src/runtime/http/cloud_middleware.dart'; +import 'package:celest_ast/celest_ast.dart' hide Variable; +import 'package:celest_cloud_auth/src/authorization/authorization_middleware.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/authorization/celest_action.dart'; +import 'package:celest_cloud_auth/src/authorization/corks_repository.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:celest_cloud_auth/src/database/schema/users.drift.dart'; +import 'package:celest_cloud_auth/src/http/http_helpers.dart'; +import 'package:celest_cloud_auth/src/model/interop.dart'; +import 'package:celest_cloud_auth/src/model/order_by.dart'; +import 'package:celest_cloud_auth/src/model/page_token.dart'; +import 'package:celest_cloud_auth/src/model/route_map.dart'; +import 'package:celest_cloud/src/proto.dart' as pb; +import 'package:celest_core/celest_core.dart'; +import 'package:drift/drift.dart' as drift; +import 'package:drift/drift.dart'; +import 'package:meta/meta.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +typedef _Deps = ({ + RouteMap routeMap, + CorksRepository corks, + AuthDatabase db, + Authorizer authorizer, +}); + +extension type UsersService._(_Deps _deps) implements Object { + UsersService({ + required RouteMap routeMap, + required CorksRepository corks, + required AuthDatabase db, + required Authorizer authorizer, + }) : this._( + ( + routeMap: routeMap, + corks: corks, + db: db, + authorizer: authorizer, + ), + ); + + AuthDatabase get _db => _deps.db; + Authorizer get _authorizer => _deps.authorizer; + + Router get _router { + return Router() + ..get('/users/', handleGetUser) + ..get('/users', handleListUsers) + ..patch('/users/', handleUpdateUser) + ..delete('/users/', handleDeleteUser); + } + + Handler get handler { + final requestAuthorizer = AuthorizationMiddleware( + routeMap: _deps.routeMap, + corks: _deps.corks, + db: _deps.db, + authorizer: _deps.authorizer, + ); + return const Pipeline() + .addMiddleware(const CloudExceptionMiddleware().call) + .addMiddleware(requestAuthorizer.call) + .addHandler(_router.call); + } + + static const String apiId = 'celest.cloud.auth.v1alpha1.Users'; + static const EntityUid apiUid = EntityUid.of('Celest::Api', apiId); + + static final ResolvedApi api = ResolvedApi( + apiId: apiId, + functions: { + 'GetUser': ResolvedCloudFunction( + apiId: apiId, + functionId: 'GetUser', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'GET', + path: '/v1alpha1/auth/{name=users/*}', + ), + ), + ), + 'ListUsers': ResolvedCloudFunction( + apiId: apiId, + functionId: 'ListUsers', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'GET', + path: '/v1alpha1/auth/users', + ), + ), + ), + 'UpdateUser': ResolvedCloudFunction( + apiId: apiId, + functionId: 'UpdateUser', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'PATCH', + path: '/v1alpha1/auth/{user.name=users/*}', + ), + ), + ), + 'DeleteUser': ResolvedCloudFunction( + apiId: apiId, + functionId: 'DeleteUser', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute( + method: 'DELETE', + path: '/v1alpha1/auth/{name=users/*}', + ), + ), + ), + }, + policySet: PolicySet( + policies: { + apiId: Policy( + effect: Effect.permit, + principal: const PrincipalIs('Celest::User'), + action: const ActionEquals(CelestAction.invoke), + annotations: Annotations({ + 'id': apiId, + }), + resource: const ResourceIn(EntityUid.of('Celest::Api', apiId)), + conditions: [], + ), + }, + ), + ); + + static final RegExp _validUserId = RegExp(r'^[a-zA-Z0-9_-]+$'); + + EntityUid _getResource(Request request) { + final userId = request.params['userId']; + if (userId == null) { + throw BadRequestException( + 'Invalid name. Expected $_validUserId, got "$userId"', + ); + } + if (!_validUserId.hasMatch(userId)) { + throw BadRequestException( + 'Invalid name. Expected $_validUserId, got "$userId"', + ); + } + return EntityUid.of('Celest::User', userId); + } + + @visibleForTesting + Future getUser({ + required String userId, + }) async { + final user = await _db.getUser(userId: userId); + if (user == null) { + throw NotFoundException('User not found. userId=$userId'); + } + return user; + } + + Future handleGetUser(Request request) async { + final principal = context.get(ContextKey.principal); + final resource = _getResource(request); + await _authorizer.expectAuthorized( + principal: principal?.uid, + resource: resource, + action: CelestAction.get, + ); + final response = await getUser(userId: principal!.userId); + return response.toProto().jsonResponse(); + } + + @visibleForTesting + Future listUsers({ + String? parent, + int? pageSize, + String? pageToken, + String? filter, + String? orderBy, + }) async { + final pageData = pageToken != null ? PageToken.parse(pageToken) : null; + final pageOffset = pageData?.offset ?? 0; + const defaultPageSize = 10; + pageSize ??= defaultPageSize; + final startTime = pageData?.startTime ?? + DateTime.timestamp().add(const Duration(seconds: 1)); + + /* + Roughly, we're trying to construct the query: + + WITH rowed AS( + SELECT + ROW_NUMBER() OVER (ORDER BY create_time DESC) AS row_num, + id, + create_time + FROM users + WHERE create_time <= coalesce(:start_time, unixepoch('now', '+1 second', 'subsec')) + ) + SELECT + row_num, + users.* + FROM users + JOIN rowed ON users.id = rowed.id + WHERE row_num > coalesce(:page_offset, 0) + ORDER BY :order_by + LIMIT :page_size; + + Since we want a dynamic `ORDER BY` clause, we need to construct the query + programmatically. We can't pass `:order_by` as a variable to Drift. + */ + + const rowNum = CustomExpression( + 'ROW_NUMBER() OVER (ORDER BY create_time DESC)', + ); + final rowedQuery = Subquery( + _db.users.selectOnly() + ..addColumns([ + rowNum, + _db.users.userId, + _db.users.createTime, + ]) + ..where( + _db.users.createTime.isSmallerThanValue(startTime), + ), + 'rowed', + ); + + final query = _db.usersDrift.select(_db.users).join([ + innerJoin( + rowedQuery, + _db.users.userId.equalsExp(rowedQuery.ref(_db.users.userId)), + useColumns: false, + ), + ]) + ..addColumns([rowedQuery.ref(rowNum)]) + ..where( + rowedQuery.ref(rowNum).isBiggerThanValue(pageOffset), + ); + + if (orderBy != null) { + final clause = OrderByClause.parse(orderBy); + query.orderBy(clause.toOrderingTerms(_db.users).toList()); + } + + query.limit(pageSize); + final rows = await query.get(); + final users = + rows.map((user) => user.readTable(_db.users).toProto()).toList(); + final nextPageToken = rows.isEmpty || rows.length < pageSize + ? null + : PageToken( + startTime: startTime, + offset: rows.last.read(rowedQuery.ref(rowNum))!, + ).encode(); + return pb.ListUsersResponse( + users: users, + nextPageToken: nextPageToken, + ); + } + + Future handleListUsers(Request request) async { + final principal = context.get(ContextKey.principal); + await _authorizer.expectAuthorized( + principal: principal?.uid, + action: CelestAction.list, + // resource: parent.uid, + debug: true, + ); + final response = await listUsers( + parent: request.url.queryParameters['parent'], + filter: request.url.queryParameters['filter'], + orderBy: request.url.queryParameters['orderBy'], + pageSize: switch (request.url.queryParameters['pageSize']) { + final pageSize? => int.parse(pageSize), + _ => null, + }, + pageToken: request.url.queryParameters['pageToken'], + ); + return response.jsonResponse(); + } + + @visibleForTesting + Future updateUser({ + required String userId, + String? givenName, + String? familyName, + String? timeZone, + String? languageCode, + List? updateMask, + }) async { + Value mask(String field, T value) { + final include = updateMask == null || + updateMask.isEmpty || + updateMask.contains(field); + if (!include) { + return const Value.absent(); + } + return Value(value); + } + + final update = UsersCompanion( + givenName: mask('given_name', givenName), + familyName: mask('family_name', familyName), + timeZone: mask('time_zone', timeZone), + languageCode: mask('language_code', languageCode), + updateTime: Value(DateTime.timestamp()), + ); + + final updated = await (_db.update(_db.users) + ..where((tbl) => tbl.userId.equals(userId))) + .writeReturning(update); + return updated.single; + } + + Future handleUpdateUser(Request request) async { + final principal = context.get(ContextKey.principal); + final resource = _getResource(request); + await _authorizer.expectAuthorized( + principal: principal?.uid, + resource: resource, + action: CelestAction.update, + ); + final jsonRequest = await JsonUtf8.decodeStream(request.read()); + final pbRequest = pb.User()..mergeFromProto3Json(jsonRequest); + final updateMask = request.url.queryParametersAll['updateMask'] + ?.expand((it) => it.split(',')); + final response = await updateUser( + userId: resource.id, + givenName: pbRequest.hasGivenName() ? pbRequest.givenName : null, + familyName: pbRequest.hasFamilyName() ? pbRequest.familyName : null, + timeZone: pbRequest.hasTimeZone() ? pbRequest.timeZone : null, + languageCode: pbRequest.hasLanguageCode() ? pbRequest.languageCode : null, + updateMask: updateMask?.toList(), + ); + return response.toProto().jsonResponse(); + } + + @visibleForTesting + Future deleteUser({ + required String userId, + String? etag, + }) async { + final deleteEntities = await _db.transaction(() async { + final user = await _db.getUser(userId: userId); + if (user == null) { + throw NotFoundException('User not found. id=$userId'); + } + if (etag != null && user.etag != etag) { + throw const FailedPreconditionException('Etag mismatch'); + } + + return (_db.delete(_db.users)..where((t) => t.userId.equals(userId))) + .go(); + }); + switch (deleteEntities) { + case 0: + throw NotFoundException('User not found. id=$userId'); + case 1: + return; + default: + context.logger.shout( + 'Deleted $deleteEntities entities for user $userId', + ); + } + } + + Future handleDeleteUser(Request request) async { + final principal = context.get(ContextKey.principal); + final resource = _getResource(request); + await _authorizer.expectAuthorized( + principal: principal?.uid, + resource: resource, + action: CelestAction.delete, + ); + await deleteUser( + userId: resource.id, + etag: request.url.queryParameters['etag'], + ); + return pb.Empty().jsonResponse(); + } +} + +extension on User { + EntityUid get uid => EntityUid.of('Celest::User', userId); +} diff --git a/services/celest_cloud_auth/lib/src/util/random_bytes.dart b/services/celest_cloud_auth/lib/src/util/random_bytes.dart new file mode 100644 index 00000000..9ef42f9e --- /dev/null +++ b/services/celest_cloud_auth/lib/src/util/random_bytes.dart @@ -0,0 +1,13 @@ +import 'dart:math'; +import 'dart:typed_data'; + +final _random = Random.secure(); + +/// Fills a [Uint8List] with secure random data of [length] bytes. +Uint8List secureRandomBytes(int length) { + final bytes = Uint8List(length); + for (var i = 0; i < bytes.length; i++) { + bytes[i] = _random.nextInt(256); + } + return bytes; +} diff --git a/services/celest_cloud_auth/lib/src/util/typeid.dart b/services/celest_cloud_auth/lib/src/util/typeid.dart new file mode 100644 index 00000000..b1c3986e --- /dev/null +++ b/services/celest_cloud_auth/lib/src/util/typeid.dart @@ -0,0 +1,198 @@ +import 'dart:typed_data'; + +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_core/_internal.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:corks_cedar/corks_cedar.dart'; + +String typeId([String? type]) => TypeId(type).encoded; + +typedef TypeIdData = ({ + String type, + Uuid uuid, + String? encoded, +}); + +extension type const Id(Object id) {} + +extension type TypeId._(TypeIdData _data) implements Id { + factory TypeId([String? type]) { + type ??= _knownTypes[T]; + type ??= ''; + final id = Uuid.v7(); + return TypeId._( + ( + type: type, + uuid: id, + encoded: type.isEmpty ? id.encoded : '${type}_${id.encoded}', + ), + ); + } + + factory TypeId.decode(String encoded) { + final codeUnits = encoded.codeUnits; + for (var i = 0; i < codeUnits.length; i++) { + const divider = 0x5f; // `_` + if (codeUnits[i] == divider) { + return TypeId._( + ( + type: String.fromCharCodes(codeUnits.sublist(0, i)), + uuid: _Uuid.decode(codeUnits.sublist(i + 1)), + encoded: encoded, + ), + ); + } + } + throw FormatException('Invalid TypeId: $encoded'); + } + + static const Map _knownTypes = { + Session: 'sess', + User: 'usr', + Cork: 'cork', + }; + + String get type => _data.type; + Uuid get uuid => _data.uuid; + + String get encoded => _data.encoded ?? _encode(); + + String _encode() { + if (type.isEmpty) { + return uuid.encoded; + } + return '${type}_${uuid.encoded}'; + } +} + +final _alphabet = '0123456789abcdefghjkmnpqrstvwxyz'.codeUnits; + +extension _Uuid on Uuid { + static Uuid decode(List bytes) { + if (bytes.length != 26) { + throw FormatException( + 'Invalid base32 length: ${bytes.length}', + String.fromCharCodes(bytes), + ); + } + + // Check if all the characters are part of the expected base32 character set + for (var offset = 0; offset < 26; offset++) { + final byte = bytes[offset]; + if (_dec[byte] == 0xff) { + throw FormatException( + 'Invalid base32 character: $byte (offset=$offset)', + String.fromCharCodes(bytes), + ); + } + } + + final id = Uint8List(16); + + // 6 bytes timestamp (48 bits) + id[0] = (_dec[bytes[0]] << 5) | _dec[bytes[1]]; + id[1] = (_dec[bytes[2]] << 3) | (_dec[bytes[3]] >> 2); + id[2] = + (_dec[bytes[3]] << 6) | (_dec[bytes[4]] << 1) | (_dec[bytes[5]] >> 4); + id[3] = (_dec[bytes[5]] << 4) | (_dec[bytes[6]] >> 1); + id[4] = + (_dec[bytes[6]] << 7) | (_dec[bytes[7]] << 2) | (_dec[bytes[8]] >> 3); + id[5] = (_dec[bytes[8]] << 5) | _dec[bytes[9]]; + + // 10 bytes of entropy (80 bits) + id[6] = (_dec[bytes[10]] << 3) | + (_dec[bytes[11]] >> 2); // First 4 bits are the version + id[7] = (_dec[bytes[11]] << 6) | + (_dec[bytes[12]] << 1) | + (_dec[bytes[13]] >> 4); + id[8] = (_dec[bytes[13]] << 4) | + (_dec[bytes[14]] >> 1); // First 2 bits are the variant + id[9] = (_dec[bytes[14]] << 7) | + (_dec[bytes[15]] << 2) | + (_dec[bytes[16]] >> 3); + id[10] = (_dec[bytes[16]] << 5) | _dec[bytes[17]]; + id[11] = (_dec[bytes[18]] << 3) | _dec[bytes[19]] >> 2; + id[12] = (_dec[bytes[19]] << 6) | + (_dec[bytes[20]] << 1) | + (_dec[bytes[21]] >> 4); + id[13] = (_dec[bytes[21]] << 4) | (_dec[bytes[22]] >> 1); + id[14] = (_dec[bytes[22]] << 7) | + (_dec[bytes[23]] << 2) | + (_dec[bytes[24]] >> 3); + id[15] = (_dec[bytes[24]] << 5) | _dec[bytes[25]]; + + return Uuid(id); + } + + /// Encodes a [value] as a base32 string. + String get encoded { + if (value.length != 16) { + throw FormatException( + 'Invalid UUID. Expected 16 bytes, got ${value.length}.', + ); + } + + final result = Uint8List(26); + + // 10 byte timestamp + result[0] = _alphabet[(value[0] & 224) >> 5]; + result[1] = _alphabet[value[0] & 31]; + result[2] = _alphabet[(value[1] & 248) >> 3]; + result[3] = _alphabet[((value[1] & 7) << 2) | ((value[2] & 192) >> 6)]; + result[4] = _alphabet[(value[2] & 62) >> 1]; + result[5] = _alphabet[((value[2] & 1) << 4) | ((value[3] & 240) >> 4)]; + result[6] = _alphabet[((value[3] & 15) << 1) | ((value[4] & 128) >> 7)]; + result[7] = _alphabet[(value[4] & 124) >> 2]; + result[8] = _alphabet[((value[4] & 3) << 3) | ((value[5] & 224) >> 5)]; + result[9] = _alphabet[value[5] & 31]; + + // 16 bytes of entropy + result[10] = _alphabet[(value[6] & 248) >> 3]; + result[11] = _alphabet[((value[6] & 7) << 2) | ((value[7] & 192) >> 6)]; + result[12] = _alphabet[(value[7] & 62) >> 1]; + result[13] = _alphabet[((value[7] & 1) << 4) | ((value[8] & 240) >> 4)]; + result[14] = _alphabet[((value[8] & 15) << 1) | ((value[9] & 128) >> 7)]; + result[15] = _alphabet[(value[9] & 124) >> 2]; + result[16] = _alphabet[((value[9] & 3) << 3) | ((value[10] & 224) >> 5)]; + result[17] = _alphabet[value[10] & 31]; + result[18] = _alphabet[(value[11] & 248) >> 3]; + result[19] = _alphabet[((value[11] & 7) << 2) | ((value[12] & 192) >> 6)]; + result[20] = _alphabet[(value[12] & 62) >> 1]; + result[21] = _alphabet[((value[12] & 1) << 4) | ((value[13] & 240) >> 4)]; + result[22] = _alphabet[((value[13] & 15) << 1) | ((value[14] & 128) >> 7)]; + result[23] = _alphabet[(value[14] & 124) >> 2]; + result[24] = _alphabet[((value[14] & 3) << 3) | ((value[15] & 224) >> 5)]; + result[25] = _alphabet[value[15] & 31]; + + return String.fromCharCodes(result); + } +} + +const _dec = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, // + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, // + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14, // + 0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, // + 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +]; diff --git a/services/celest_cloud_auth/pubspec.yaml b/services/celest_cloud_auth/pubspec.yaml new file mode 100644 index 00000000..0b75e26e --- /dev/null +++ b/services/celest_cloud_auth/pubspec.yaml @@ -0,0 +1,41 @@ +name: celest_cloud_auth +description: The Celest authentication and authorization service. +version: 0.1.0 + +environment: + sdk: ^3.4.0 + +dependencies: + args: ^2.4.0 + async: ^2.11.0 + cedar: ^0.2.2 + celest: ^1.0.0-0 + celest_ast: ^0.1.0 + celest_cloud: ^0.1.0 + celest_core: ^1.0.0-0 + clock: ^1.1.1 + collection: ^1.18.0 + convert: ^3.1.1 + corks_cedar: ^0.2.1 + crypto: ^3.0.5 + drift: ^2.20.0 + file: ^7.0.1 + fixnum: ^1.1.0 + http: ^1.0.0 + logging: ^1.2.0 + meta: ^1.15.0 + mustache_template: ^2.0.0 + path: ^1.9.0 + petitparser: ^6.0.2 + protobuf: ^3.1.0 + pub_semver: ^2.1.4 + shelf: ^1.4.0 + shelf_router: ^1.1.0 + sqlite3: ^2.4.6 + +dev_dependencies: + build_runner: ^2.4.13 + celest_lints: ^1.0.0 + checks: ^0.3.0 + drift_dev: ^2.20.3 + test: ^1.24.0 diff --git a/services/celest_cloud_auth/test/authentication/authentication_service_test.dart b/services/celest_cloud_auth/test/authentication/authentication_service_test.dart new file mode 100644 index 00000000..9e31742f --- /dev/null +++ b/services/celest_cloud_auth/test/authentication/authentication_service_test.dart @@ -0,0 +1,412 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:celest_cloud/celest_cloud.dart' as pb; +import 'package:celest_cloud_auth/src/authentication/authentication_model.dart'; +import 'package:celest_cloud_auth/src/model/interop.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:checks/checks.dart'; +import 'package:clock/clock.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:test/test.dart'; + +import '../tester.dart'; + +void main() { + final tester = AuthorizationTester(persistData: true); + + group('AuthenticationService', () { + tester.setUp(); + + group('getOpenIdUserInfo', () { + const route = ('GET', '/v1alpha1/auth/userinfo'); + + test('unauthenticated', () async { + await tester.httpTest({ + route: expectStatus(401), + }); + }); + + test('authenticated', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + route: expectAll([ + expectStatus(200), + expectBody({ + 'sub': userId, + }), + ]), + }); + }); + + test('admin', () async { + final userId = typeId(); + const email = 'admin@celest.dev'; + final user = await tester.db.createUser( + user: User( + userId: userId, + emails: const [Email(email: email)], + roles: const [roleAdmin], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + route: expectAll([ + expectStatus(200), + expectBody({ + 'sub': userId, + 'email': email, + 'email_verified': false, + }), + ]), + }); + }); + }); + + test('create account', () async { + final email = 'test-${Random().nextInt(100000)}@celest.dev'; + + final session = await tester.authenticationService.startSession( + factor: AuthenticationFactorEmailOtp(email: email), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + check(() => session.sessionToken).returnsNormally(); + check(session.state) + .isA() + .has((s) => s.factor, 'factor') + .isA(); + + final (to: _, :code) = tester.lastSentCode!; + + final result = await tester.authenticationService.continueSession( + sessionId: session.sessionId, + sessionToken: session.sessionToken, + proof: AuthenticationFactorEmailOtp(email: email, code: code), + ); + + check(result.state).isA() + ..has((s) => s.isNewUser, 'isNewUser').isTrue() + ..has((s) => s.user.primaryEmail, 'email').equals( + Email(email: email, isVerified: true, isPrimary: true), + ) + ..has( + (s) => CedarCork(s.cork).bearer.id == s.user.userId, + 'cork bearer == userId', + ).isTrue(); + }); + + test('re-authenticate', () async { + final email = 'test-${Random().nextInt(100000)}@celest.dev'; + + final session = await tester.authenticationService.startSession( + factor: AuthenticationFactorEmailOtp(email: email), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + check(() => session.sessionToken).returnsNormally(); + check(session.state) + .isA() + .has((s) => s.factor, 'factor') + .isA(); + + final (to: _, :code) = tester.lastSentCode!; + + final result = await tester.authenticationService.continueSession( + sessionId: session.sessionId, + sessionToken: session.sessionToken, + proof: AuthenticationFactorEmailOtp(email: email, code: code), + ); + + check(result.state).isA() + ..has((s) => s.isNewUser, 'isNewUser').isTrue() + ..has((s) => s.user.primaryEmail, 'email').equals( + Email(email: email, isVerified: true, isPrimary: true), + ); + + final session2 = await tester.authenticationService.startSession( + factor: AuthenticationFactorEmailOtp(email: email), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + check(() => session2.sessionToken).returnsNormally(); + check(session2.state) + .isA() + .has((s) => s.factor, 'factor') + .isA(); + + final (to: _, code: code2) = tester.lastSentCode!; + + final result2 = await tester.authenticationService.continueSession( + sessionId: session2.sessionId, + sessionToken: session2.sessionToken, + proof: AuthenticationFactorEmailOtp(email: email, code: code2), + ); + + check(result2.state).isA() + ..has((s) => s.isNewUser, 'isNewUser').isFalse() + ..has((s) => s.user, 'user') + .equals((result.state as SessionStateSuccess).user); + }); + + test('resend otp', () async { + final email = 'test-${Random().nextInt(100000)}@celest.dev'; + + check(tester.lastSentCode).isNull(); + + final session = await tester.authenticationService.startSession( + factor: AuthenticationFactorEmailOtp(email: email), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + check(() => session.sessionToken).returnsNormally(); + check(session.state) + .isA() + .has((s) => s.factor, 'factor') + .isA(); + + check(tester.lastSentCode).isNotNull(); + + final nextResend = DateTime.now().add(const Duration(minutes: 1)); + await withClock(Clock.fixed(nextResend), () async { + final result = await tester.authenticationService.continueSession( + sessionId: session.sessionId, + sessionToken: session.sessionToken, + resend: AuthenticationFactorEmailOtp(email: email), + ); + check(result.state).isA(); + }); + + final (to: _, :code) = tester.lastSentCode!; + + final result2 = await tester.authenticationService.continueSession( + sessionId: session.sessionId, + sessionToken: session.sessionToken, + proof: AuthenticationFactorEmailOtp(email: email, code: code), + ); + + check(result2.state).isA() + ..has((s) => s.isNewUser, 'isNewUser').isTrue() + ..has((s) => s.user.primaryEmail, 'email').equals( + Email(email: email, isVerified: true, isPrimary: true), + ); + }); + + group('startSession', () { + const route = ('POST', '/v1alpha1/auth/sessions:startSession'); + + group('cloud', () { + test('unauthenticated', () async { + final cloud = tester.cloud(); + await check( + cloud.authentication.email.start(email: 'test@celest.dev'), + ).completes(); + }); + + test('authenticated', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + + final cloud = tester.cloud(cork: cork); + await check( + cloud.authentication.email.start(email: 'test@celest.dev'), + ).completes(); + }); + + test('create account', () async { + const email = 'test@celest.dev'; + final cloud = tester.cloud(); + + final state = await cloud.authentication.email.start(email: email); + check(state) + .isA() + .has((it) => it.sessionToken, 'sessionToken') + .isNotEmpty(); + + final (to: _, :code) = tester.lastSentCode!; + await check( + cloud.authentication.email.verifyCode( + state: state as pb.EmailSessionVerifyCode, + code: code, + ), + ).completes( + (result) => result.isA() + ..has((it) => it.isNewUser, 'isNewUser').isTrue() + ..has((it) => it.user.toModel().primaryEmail, 'email').equals( + const Email(email: email, isVerified: true, isPrimary: true), + ), + ); + }); + }); + + group('authorization', () { + const request = { + 'emailOtp': { + 'email': 'test@celest.dev', + }, + 'client': { + 'clientId': 'test', + 'clientType': 'HEADLESS', + 'callbacks': { + 'successUri': 'https://celest.dev', + }, + }, + }; + + test('unauthenticated', () async { + await tester.httpTest(body: request, { + route: expectAll([ + expectStatus(200), + expectBodyHas([ + (it) => it.containsKey('sessionId'), + (it) => it.containsKey('sessionToken'), + (it) => it.containsKey('expireTime'), + (it) => it + .has((it) => it['nextStep'], 'nextStep') + .isA>() + .deepEquals({ + 'needsProof': { + 'emailOtp': { + 'email': 'test@celest.dev', + }, + }, + }), + ]), + ]), + }); + check(tester.lastSentCode) + .isNotNull() + .has((it) => it.to, 'to') + .equals('test@celest.dev'); + }); + + test('re-authenticate', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, body: request, { + route: expectAll([ + expectStatus(200), + expectBodyHas([ + (it) => it.containsKey('sessionId'), + (it) => it.containsKey('sessionToken'), + (it) => it.containsKey('expireTime'), + (it) => it + .has((it) => it['nextStep'], 'nextStep') + .isA>() + .deepEquals({ + 'needsProof': { + 'emailOtp': { + 'email': 'test@celest.dev', + }, + }, + }), + ]), + ]), + }); + check(tester.lastSentCode) + .isNotNull() + .has((it) => it.to, 'to') + .equals('test@celest.dev'); + }); + }); + }); + + group('endSession', () { + test('unauthenticated', () async { + final session = await tester.authenticationService.startSession( + factor: const AuthenticationFactorEmailOtp(email: 'test@celest.dev'), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + check(() => session.sessionToken).returnsNormally(); + await check( + tester.authenticationService.endSession( + sessionId: session.sessionId, + sessionToken: session.sessionToken, + ), + ).completes(); + }); + + test('bad sessionToken', skip: true, () async { + final session = await tester.authenticationService.startSession( + factor: const AuthenticationFactorEmailOtp(email: 'test@celest.dev'), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ); + + final cork = await tester.corks.createSessionCork( + session: Session( + sessionId: typeId(), + cryptoKeyId: Uint8List(0), + expireTime: DateTime.now().add(const Duration(days: 1)), + authenticationFactor: const AuthenticationFactorEmailOtp( + email: 'test@celest.dev', + ), + clientInfo: SessionClient( + clientId: 'test', + callbacks: SessionCallbacks( + successUri: Uri.parse('https://celest.dev'), + ), + ), + ), + ); + + await check( + tester.authenticationService.endSession( + sessionId: session.sessionId, + sessionToken: cork.toString(), + ), + ).throws(); + }); + }); + }); +} diff --git a/services/celest_cloud_auth/test/authorization/authorization_middleware_test.dart b/services/celest_cloud_auth/test/authorization/authorization_middleware_test.dart new file mode 100644 index 00000000..0c987447 --- /dev/null +++ b/services/celest_cloud_auth/test/authorization/authorization_middleware_test.dart @@ -0,0 +1,72 @@ +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:test/test.dart'; + +import '../tester.dart'; + +void main() { + final tester = AuthorizationTester(persistData: true); + + group('AuthorizationMiddleware', () { + tester.setUp(); + + const public = ('POST', '/test/public'); + const authenticated = ('POST', '/test/authenticated'); + const admin = ('POST', '/test/admin'); + + test('unauthenticated', () async { + await tester.httpTest({ + public: expectStatus(200), + authenticated: expectStatus(403), + admin: expectStatus(403), + }); + }); + + test('anonymous', () async { + final userId = typeId(); + final cork = await tester.corks.createUserCork( + user: User( + userId: userId, + roles: const [roleAnonymous], + ), + ); + await tester.httpTest(cork: cork, { + public: expectStatus(200), + authenticated: expectStatus(403), + admin: expectStatus(403), + }); + }); + + test('authenticated', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + public: expectStatus(200), + authenticated: expectStatus(200), + admin: expectStatus(403), + }); + }); + + test('admin', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAdmin], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + public: expectStatus(200), + authenticated: expectStatus(200), + admin: expectStatus(200), + }); + }); + }); +} diff --git a/services/celest_cloud_auth/test/authorization/authorizer_test.dart b/services/celest_cloud_auth/test/authorization/authorizer_test.dart new file mode 100644 index 00000000..2dfb967f --- /dev/null +++ b/services/celest_cloud_auth/test/authorization/authorizer_test.dart @@ -0,0 +1,771 @@ +import 'package:cedar/ast.dart'; +import 'package:cedar/cedar.dart'; +import 'package:celest_ast/celest_ast.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/database/auth_database.dart'; +import 'package:logging/logging.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +import '../tester.dart'; + +// Roles +const roleAdmin = EntityUid.of('Celest::Role', 'admin'); +const roleAuthenticated = EntityUid.of('Celest::Role', 'authenticated'); +const roleAnonymous = EntityUid.of('Celest::Role', 'anonymous'); + +// Users +const userAlice = EntityUid.of('Celest::User', 'alice'); +const userBob = EntityUid.of('Celest::User', 'bob'); +const userCharlie = EntityUid.of('Celest::User', 'charlie'); + +// Actions +const actionCreate = EntityUid.of('Celest::Action', 'create'); +const actionGet = EntityUid.of('Celest::Action', 'get'); +const actionUpdate = EntityUid.of('Celest::Action', 'update'); +const actionDelete = EntityUid.of('Celest::Action', 'delete'); +const actionList = EntityUid.of('Celest::Action', 'list'); +const actionInvoke = EntityUid.of('Celest::Action', 'invoke'); + +typedef TestCase = ({ + String description, + AuthorizationRequest request, + Decision expected, +}); + +/// Mirrors the policy generated by the API to ensure that metadata at the +/// function level never overrides the API level. +Policy forbidUnless( + EntityUid resource, { + required String id, + required EntityUid unlessRole, +}) => + Policy( + effect: Effect.forbid, + principal: const PrincipalAll(), + action: const ActionEquals(actionInvoke), + resource: ResourceIn(resource), + annotations: Annotations({ + 'id': id, + }), + conditions: [ + Condition( + kind: ConditionKind.unless, + body: Expr.in_( + left: const Expr.variable(CedarVariable.principal), + right: Expr.value( + Value.entity(uid: unlessRole), + ), + ), + ), + ], + ); + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.message}'); + }); + + group('Authorizer', () { + late AuthDatabase db; + late Authorizer authorizer; + + Future createEntities(List entities) async { + for (final entity in entities) { + await db.createEntity(entity); + } + } + + void run( + List testCases, { + required List entities, + required ResolvedProject project, + }) { + setUpAll(() async { + db = AuthDatabase.memory(project: project); + await db.ping(); + await createEntities(entities); + addTearDown(db.close); + + authorizer = Authorizer(db: db); + }); + + for (final (:description, :request, :expected) in testCases) { + test(description, () async { + final response = await authorizer.authorize( + principal: request.principal, + action: request.action, + context: request.context, + resource: request.resource, + debug: true, + ); + if (response.decision != expected) { + fail( + 'Expected $expected but got ${response.decision}: \n' + 'Errors: ${response.errors}\n' + 'Reasons: ${response.reasons}', + ); + } + }); + } + } + + group('function auth', () { + const apiTest = EntityUid.of('Celest::Api', 'test'); + + const functionAdmin = EntityUid.of('Celest::Function', 'test/admin'); + const functionAuthenticated = + EntityUid.of('Celest::Function', 'test/authenticated'); + const functionPublic = EntityUid.of('Celest::Function', 'test/public'); + + const entities = [ + Entity(uid: userAlice, parents: [roleAdmin]), + Entity(uid: userBob, parents: [roleAuthenticated]), + Entity(uid: userCharlie, parents: [roleAnonymous]), + Entity(uid: functionAuthenticated, parents: [apiTest]), + Entity(uid: functionAdmin, parents: [apiTest]), + Entity(uid: functionPublic, parents: [apiTest]), + ]; + + run(project: defaultProject, entities: entities, const [ + ( + description: 'unauthenticated user cannot invoke admin function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAdmin, + ), + expected: Decision.deny, + ), + ( + description: + 'unauthenticated user cannot invoke authenticated function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAuthenticated, + ), + expected: Decision.deny, + ), + ( + description: 'unauthenticated user can invoke public function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionPublic, + ), + expected: Decision.allow, + ), + ( + description: 'anonymous user cannot invoke admin function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAdmin, + principal: userCharlie, + ), + expected: Decision.deny, + ), + ( + description: 'anonymous user cannot invoke authenticated function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAuthenticated, + principal: userCharlie, + ), + expected: Decision.deny, + ), + ( + description: 'anonymous user can invoke public function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionPublic, + principal: userCharlie, + ), + expected: Decision.allow, + ), + ( + description: 'authenticated user cannot invoke admin function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAdmin, + principal: userBob, + ), + expected: Decision.deny, + ), + ( + description: 'authenticated user can invoke authenticated function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAuthenticated, + principal: userBob, + ), + expected: Decision.allow, + ), + ( + description: 'authenticated user can invoke public function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionPublic, + principal: userBob, + ), + expected: Decision.allow, + ), + ( + description: 'admin user can invoke admin function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAdmin, + principal: userAlice, + ), + expected: Decision.allow, + ), + ( + description: 'admin user can invoke authenticated function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionAuthenticated, + principal: userAlice, + ), + expected: Decision.allow, + ), + ( + description: 'admin user can invoke public function', + request: AuthorizationRequest( + action: actionInvoke, + resource: functionPublic, + principal: userAlice, + ), + expected: Decision.allow, + ), + ]); + }); + + group('api auth', () { + const apiAdmin = EntityUid.of('Celest::Api', 'admin'); + const apiAuthenticated = EntityUid.of('Celest::Api', 'authenticated'); + const apiPublic = EntityUid.of('Celest::Api', 'public'); + + const adminFunction = EntityUid.of('Celest::Function', 'admin/admin'); + const adminFunctionAdmin = + EntityUid.of('Celest::Function', 'admin/adminAdmin'); + const adminFunctionAuthenticated = + EntityUid.of('Celest::Function', 'admin/adminAuthenticated'); + const adminFunctionPublic = + EntityUid.of('Celest::Function', 'admin/adminPublic'); + + const authenticatedFunction = + EntityUid.of('Celest::Function', 'authenticated/authenticated'); + const authenticatedFunctionAdmin = + EntityUid.of('Celest::Function', 'authenticated/authenticatedAdmin'); + const authenticatedFunctionAuthenticated = EntityUid.of( + 'Celest::Function', + 'authenticated/authenticatedAuthenticated', + ); + const authenticatedFunctionPublic = + EntityUid.of('Celest::Function', 'authenticated/authenticatedPublic'); + + const publicFunction = EntityUid.of('Celest::Function', 'public/public'); + const publicFunctionAdmin = + EntityUid.of('Celest::Function', 'public/publicAdmin'); + const publicFunctionAuthenticated = + EntityUid.of('Celest::Function', 'public/publicAuthenticated'); + const publicFunctionPublic = + EntityUid.of('Celest::Function', 'public/publicPublic'); + + final project = ResolvedProject( + projectId: 'test', + environmentId: 'production', + sdkConfig: SdkConfiguration( + celest: Version(1, 0, 0), + dart: Sdk( + type: SdkType.dart, + version: Version(3, 5, 0), + ), + ), + apis: { + 'admin': ResolvedApi( + apiId: 'admin', + policySet: PolicySet( + policies: { + 'api-admin_restrict': forbidUnless( + apiAdmin, + id: 'api-admin_restrict', + unlessRole: roleAdmin, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.admin', + newId: 'api-admin', + values: { + SlotId.resource: apiAdmin, + }, + ), + ], + ), + functions: { + 'admin': ResolvedCloudFunction( + apiId: 'admin', + functionId: 'admin', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/admin/test'), + ), + ), + 'adminAdmin': ResolvedCloudFunction( + apiId: 'admin', + functionId: 'adminAdmin', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/admin/admin'), + ), + policySet: PolicySet( + policies: { + 'admin-admin_restrict': forbidUnless( + adminFunctionAdmin, + id: 'admin-admin_restrict', + unlessRole: roleAdmin, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.admin', + newId: 'admin-admin', + values: { + SlotId.resource: adminFunctionAdmin, + }, + ), + ], + ), + ), + 'adminAuthenticated': ResolvedCloudFunction( + apiId: 'admin', + functionId: 'adminAuthenticated', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/admin/authenticated'), + ), + policySet: PolicySet( + policies: { + 'admin-authenticated_restrict': forbidUnless( + adminFunctionAuthenticated, + id: 'admin-authenticated_restrict', + unlessRole: roleAuthenticated, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.authenticated', + newId: 'admin-authenticated', + values: { + SlotId.resource: adminFunctionAuthenticated, + }, + ), + ], + ), + ), + 'adminPublic': ResolvedCloudFunction( + apiId: 'admin', + functionId: 'adminPublic', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/admin/public'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: 'admin-public', + values: { + SlotId.resource: adminFunctionPublic, + }, + ), + ], + ), + ), + }, + ), + 'authenticated': ResolvedApi( + apiId: 'authenticated', + policySet: PolicySet( + policies: { + 'api-authenticated_restrict': forbidUnless( + apiAuthenticated, + id: 'api-authenticated_restrict', + unlessRole: roleAuthenticated, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.authenticated', + newId: 'api-authenticated', + values: { + SlotId.resource: apiAuthenticated, + }, + ), + ], + ), + functions: { + 'authenticated': ResolvedCloudFunction( + apiId: 'authenticated', + functionId: 'authenticated', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/authenticated/test'), + ), + ), + 'authenticatedAdmin': ResolvedCloudFunction( + apiId: 'authenticated', + functionId: 'authenticatedAdmin', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/authenticated/admin'), + ), + policySet: PolicySet( + policies: { + 'authenticated-admin_restrict': forbidUnless( + authenticatedFunctionAdmin, + id: 'authenticated-admin_restrict', + unlessRole: roleAdmin, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.admin', + newId: 'authenticated-admin', + values: { + SlotId.resource: authenticatedFunctionAdmin, + }, + ), + ], + ), + ), + 'authenticatedAuthenticated': ResolvedCloudFunction( + apiId: 'authenticated', + functionId: 'authenticatedAuthenticated', + httpConfig: ResolvedHttpConfig( + route: + ResolvedHttpRoute(path: '/authenticated/authenticated'), + ), + policySet: PolicySet( + policies: { + 'authenticated-authenticated_restrict': forbidUnless( + authenticatedFunctionAuthenticated, + id: 'authenticated-authenticated_restrict', + unlessRole: roleAuthenticated, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.authenticated', + newId: 'authenticated-authenticated', + values: { + SlotId.resource: authenticatedFunctionAuthenticated, + }, + ), + ], + ), + ), + 'authenticatedPublic': ResolvedCloudFunction( + apiId: 'authenticated', + functionId: 'authenticatedPublic', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/authenticated/public'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: 'authenticated-public', + values: { + SlotId.resource: authenticatedFunctionPublic, + }, + ), + ], + ), + ), + }, + ), + 'public': ResolvedApi( + apiId: 'public', + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: 'api-public', + values: { + SlotId.resource: apiPublic, + }, + ), + ], + ), + functions: { + 'public': ResolvedCloudFunction( + apiId: 'public', + functionId: 'public', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/public/test'), + ), + ), + 'publicAdmin': ResolvedCloudFunction( + apiId: 'public', + functionId: 'publicAdmin', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/public/admin'), + ), + policySet: PolicySet( + policies: { + 'public-admin_restrict': forbidUnless( + publicFunctionAdmin, + id: 'public-admin_restrict', + unlessRole: roleAdmin, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.admin', + newId: 'public-admin', + values: { + SlotId.resource: publicFunctionAdmin, + }, + ), + ], + ), + ), + 'publicAuthenticated': ResolvedCloudFunction( + apiId: 'public', + functionId: 'publicAuthenticated', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/public/authenticated'), + ), + policySet: PolicySet( + policies: { + 'public-authenticated_restrict': forbidUnless( + publicFunctionAuthenticated, + id: 'public-authenticated_restrict', + unlessRole: roleAuthenticated, + ), + }, + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.authenticated', + newId: 'public-authenticated', + values: { + SlotId.resource: publicFunctionAuthenticated, + }, + ), + ], + ), + ), + 'publicPublic': ResolvedCloudFunction( + apiId: 'public', + functionId: 'publicPublic', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/public/public'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: 'public-public', + values: { + SlotId.resource: publicFunctionPublic, + }, + ), + ], + ), + ), + }, + ), + }, + ); + + const entities = [ + Entity(uid: userAlice, parents: [roleAdmin]), + Entity(uid: userBob, parents: [roleAuthenticated]), + Entity(uid: userCharlie, parents: [roleAnonymous]), + Entity(uid: adminFunction, parents: [apiAdmin]), + Entity(uid: adminFunctionAuthenticated, parents: [apiAdmin]), + Entity(uid: adminFunctionAdmin, parents: [apiAdmin]), + Entity(uid: adminFunctionPublic, parents: [apiAdmin]), + Entity(uid: authenticatedFunction, parents: [apiAuthenticated]), + Entity( + uid: authenticatedFunctionAuthenticated, + parents: [apiAuthenticated], + ), + Entity(uid: authenticatedFunctionAdmin, parents: [apiAuthenticated]), + Entity(uid: authenticatedFunctionPublic, parents: [apiAuthenticated]), + Entity(uid: publicFunction, parents: [apiPublic]), + Entity(uid: publicFunctionAuthenticated, parents: [apiPublic]), + Entity(uid: publicFunctionAdmin, parents: [apiPublic]), + Entity(uid: publicFunctionPublic, parents: [apiPublic]), + ]; + + const adminFunctions = [ + adminFunction, + adminFunctionAdmin, + adminFunctionAuthenticated, + adminFunctionPublic, + authenticatedFunctionAdmin, + publicFunctionAdmin, + ]; + + const authenticatedFunctions = [ + authenticatedFunction, + authenticatedFunctionAuthenticated, + authenticatedFunctionPublic, + publicFunctionAuthenticated, + ]; + + const publicFunctions = [ + publicFunction, + publicFunctionPublic, + ]; + + group('unauthenticated user', () { + run( + project: project, + entities: entities, + [ + for (final adminFunction in adminFunctions) + ( + description: '$adminFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: adminFunction, + ), + expected: Decision.deny, + ), + for (final authenticatedFunction in authenticatedFunctions) + ( + description: '$authenticatedFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: authenticatedFunction, + ), + expected: Decision.deny, + ), + for (final publicFunction in publicFunctions) + ( + description: '$publicFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: publicFunction, + ), + expected: Decision.allow, + ), + ], + ); + }); + + group('anonymous user', () { + run( + project: project, + entities: entities, + [ + for (final adminFunction in adminFunctions) + ( + description: '$adminFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: adminFunction, + principal: userCharlie, + ), + expected: Decision.deny, + ), + for (final authenticatedFunction in authenticatedFunctions) + ( + description: '$authenticatedFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: authenticatedFunction, + principal: userCharlie, + ), + expected: Decision.deny, + ), + for (final publicFunction in publicFunctions) + ( + description: '$publicFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: publicFunction, + principal: userCharlie, + ), + expected: Decision.allow, + ), + ], + ); + }); + + group('authenticated user', () { + run( + project: project, + entities: entities, + [ + for (final adminFunction in adminFunctions) + ( + description: '$adminFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: adminFunction, + principal: userBob, + ), + expected: Decision.deny, + ), + for (final authenticatedFunction in authenticatedFunctions) + ( + description: '$authenticatedFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: authenticatedFunction, + principal: userBob, + ), + expected: Decision.allow, + ), + for (final publicFunction in publicFunctions) + ( + description: '$publicFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: publicFunction, + principal: userBob, + ), + expected: Decision.allow, + ), + ], + ); + }); + + group('admin user', () { + run( + project: project, + entities: entities, + [ + for (final adminFunction in adminFunctions) + ( + description: '$adminFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: adminFunction, + principal: userAlice, + ), + expected: Decision.allow, + ), + for (final authenticatedFunction in authenticatedFunctions) + ( + description: '$authenticatedFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: authenticatedFunction, + principal: userAlice, + ), + expected: Decision.allow, + ), + for (final publicFunction in publicFunctions) + ( + description: '$publicFunction', + request: AuthorizationRequest( + action: actionInvoke, + resource: publicFunction, + principal: userAlice, + ), + expected: Decision.allow, + ), + ], + ); + }); + }); + }); +} diff --git a/services/celest_cloud_auth/test/model/cookie_test.dart b/services/celest_cloud_auth/test/model/cookie_test.dart new file mode 100644 index 00000000..23477847 --- /dev/null +++ b/services/celest_cloud_auth/test/model/cookie_test.dart @@ -0,0 +1,67 @@ +import 'package:celest_cloud_auth/src/model/cookie.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.message}'); + if (record.error case final error?) { + print(error); + } + if (record.stackTrace case final stackTrace?) { + print(stackTrace); + } + }); + + group('parseCookies', () { + for (final testCase in _testCases) { + test(testCase.header, () { + final actual = parseCookies({ + 'cookie': testCase.header, + }); + expect(actual, equals(testCase.expected)); + }); + } + }); +} + +typedef _TestCase = ({ + List header, + Map expected, +}); + +const _testCases = <_TestCase>[ + ( + header: [r'Cookie-1=v$1', 'c2=v2'], + expected: { + 'Cookie-1': r'v$1', + 'c2': 'v2', + }, + ), + ( + header: [r'Cookie-1=v$1; c2=v2'], + expected: { + 'Cookie-1': r'v$1', + 'c2': 'v2', + }, + ), + ( + header: [r'Cookie-1="v$1"; c2="v2"'], + expected: { + 'Cookie-1': r'v$1', + 'c2': 'v2', + }, + ), + ( + header: [r'Cookie-1="v$1"; c2=v2;'], + expected: { + 'Cookie-1': r'v$1', + 'c2': 'v2', + }, + ), + ( + header: [''], + expected: {}, + ), +]; diff --git a/services/celest_cloud_auth/test/model/route_test.dart b/services/celest_cloud_auth/test/model/route_test.dart new file mode 100644 index 00000000..e8d68015 --- /dev/null +++ b/services/celest_cloud_auth/test/model/route_test.dart @@ -0,0 +1,341 @@ +import 'package:celest_cloud_auth/src/model/route.dart'; +import 'package:checks/checks.dart'; +import 'package:petitparser/petitparser.dart'; +import 'package:test/test.dart'; + +void main() { + group('Route', () { + test('Literal', () { + const segment = '/v1alpha1/auth/users'; + final literal = RouteLiteral(segment); + check(literal.parse(segment)) + .isA>() + .has((it) => it.value, 'value') + .equals(segment); + }); + + test('Wildcard (greedy)', () { + final wildcard = RouteWildcard(greedy: true); + check(wildcard.parse('123')) + .isA>() + .has((it) => it.value, 'value') + .equals('123'); + check(wildcard.parse('123/456')) + .isA>() + .has((it) => it.value, 'value') + .equals('123/456'); + }); + + test('Wildcard (greedy, >maxLength)', () { + final path = '123/456/789' * 1000; + final wildcard = RouteWildcard(greedy: true); + check(wildcard.parse(path)).isA(); + }); + + test('Wildcard (non-greedy)', () { + final wildcard = RouteWildcard(greedy: false); + check(wildcard.parse('123')) + .isA>() + .has((it) => it.value, 'value') + .equals('123'); + check(wildcard.parse('123/456')) + .isA>() + .has((it) => it.value, 'value') + .equals('123'); + }); + + test('Route', () { + final route = Route( + template: '/v1alpha1/auth/{name=users/*}', + segments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'name', + segments: [ + RouteLiteral('users'), + RouteWildcard(greedy: false), + ], + ), + ], + ); + check(route.parse('/v1alpha1/auth/users/123')) + .isA>>() + .has((it) => it.value, 'value') + .deepEquals({'name': 'users/123'}); + }); + + group('parse', () { + for (final testCase in _testCases) { + test(testCase.route, () { + final actual = Route.parse(testCase.route); + check(actual) + .has((it) => it.segments, 'segments') + .deepEquals(testCase.expectedSegments); + check(actual) + .has((it) => it.verb, 'verb') + .equals(testCase.expectedVerb); + check(actual.toString()).equals(testCase.route); + }); + } + }); + + group('match', () { + for (final testCase in _testCases) { + test(testCase.route, () { + final route = Route.parse(testCase.route); + + for (final matchTest in testCase.matchTests) { + final actual = route.match(matchTest.route, debug: false); + if (matchTest.expected case final expected?) { + check(actual).isNotNull().deepEquals(expected); + } else { + check(actual).isNull(); + } + } + }); + } + }); + }); +} + +typedef _TestCase = ({ + String route, + List expectedSegments, + RouteVerb? expectedVerb, + List<_MatchTest> matchTests, +}); + +typedef _MatchTest = ({ + String route, + Map? expected, +}); + +final _testCases = <_TestCase>[ + ( + route: '/v1alpha1/auth/users', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteLiteral('users'), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/users', + expected: {}, + ), + ( + route: '/v1alpha1/auth', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/users/{id=**}', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteLiteral('users'), + RouteParameter( + variable: 'id', + segments: [RouteWildcard(greedy: true)], + ), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/users/123', + expected: {'id': '123'}, + ), + ( + route: '/v1alpha1/auth/users/123/456', + expected: {'id': '123/456'}, + ), + ( + route: '/v1alpha1/auth/users', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/{name=users/*}', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'name', + segments: [ + RouteLiteral('users'), + RouteWildcard(greedy: false), + ], + ), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/users/123', + expected: {'name': 'users/123'}, + ), + ( + route: '/v1alpha1/auth/users/123/456', + expected: null, + ), + ( + route: '/v1alpha1/auth', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/{name=users/**}', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'name', + segments: [ + RouteLiteral('users'), + RouteWildcard(greedy: true), + ], + ), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/users/123', + expected: {'name': 'users/123'}, + ), + ( + route: '/v1alpha1/auth/users/123/456', + expected: {'name': 'users/123/456'}, + ), + ( + route: '/v1alpha1/auth/users', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/{name=users/*}/email', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'name', + segments: [ + RouteLiteral('users'), + RouteWildcard(greedy: false), + ], + ), + RouteLiteral('email'), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/users/123/email', + expected: {'name': 'users/123'}, + ), + ( + route: '/v1alpha1/auth/users/123/456/email', + expected: null, + ), + ( + route: '/v1alpha1/auth/users/email', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/{name=organizations/*/users/*}', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'name', + segments: [ + RouteLiteral('organizations'), + RouteWildcard(greedy: false), + RouteLiteral('users'), + RouteWildcard(greedy: false), + ], + ), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/v1alpha1/auth/organizations/123/users/456', + expected: {'name': 'organizations/123/users/456'}, + ), + ( + route: '/v1alpha1/auth/organizations/123/users', + expected: null, + ), + ( + route: '/v1alpha1/auth/organizations/users/123', + expected: null, + ), + ( + route: '/v1alpha1/auth/organizations/123/users/456/789', + expected: null, + ), + ], + ), + ( + route: '/v1alpha1/auth/{user.name=organizations/*/users/*}:updateEmail', + expectedSegments: [ + RouteLiteral('v1alpha1'), + RouteLiteral('auth'), + RouteParameter( + variable: 'user.name', + segments: [ + RouteLiteral('organizations'), + RouteWildcard(greedy: false), + RouteLiteral('users'), + RouteWildcard(greedy: false), + ], + ), + ], + expectedVerb: RouteVerb('updateEmail'), + matchTests: [ + ( + route: '/v1alpha1/auth/organizations/123/users/456:updateEmail', + expected: {'user.name': 'organizations/123/users/456'}, + ), + ( + route: '/v1alpha1/auth/organizations/123/users:updateEmail', + expected: null, + ), + ( + route: '/v1alpha1/auth/organizations/users/123:updateEmail', + expected: null, + ), + ( + route: '/v1alpha1/auth/organizations/123/users/456/789:updateEmail', + expected: null, + ), + ], + ), + ( + route: '/test/say-hello', + expectedSegments: [ + RouteLiteral('test'), + RouteLiteral('say-hello'), + ], + expectedVerb: null, + matchTests: [ + ( + route: '/test/say-hello', + expected: {}, + ), + ( + route: '/test/say-hello/', + expected: null, + ), + ( + route: '/test/say-hello/123', + expected: null, + ), + ], + ), +]; diff --git a/services/celest_cloud_auth/test/tester.dart b/services/celest_cloud_auth/test/tester.dart new file mode 100644 index 00000000..0df76c7b --- /dev/null +++ b/services/celest_cloud_auth/test/tester.dart @@ -0,0 +1,360 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:cedar/cedar.dart'; +import 'package:celest/celest.dart'; +import 'package:celest/src/runtime/serve.dart'; +import 'package:celest_ast/celest_ast.dart'; +import 'package:celest_cloud/celest_cloud.dart' show CelestCloud, ClientType; +import 'package:celest_cloud_auth/celest_cloud_auth.dart'; +import 'package:celest_cloud_auth/src/authentication/authentication_service.dart'; +import 'package:celest_cloud_auth/src/authorization/authorization_middleware.dart'; +import 'package:celest_cloud_auth/src/authorization/authorizer.dart'; +import 'package:celest_cloud_auth/src/authorization/cedar_interop.dart'; +import 'package:celest_cloud_auth/src/authorization/corks_repository.dart'; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_model.dart'; +import 'package:celest_cloud_auth/src/crypto/crypto_key_repository.dart'; +import 'package:celest_cloud_auth/src/users/users_service.dart'; +import 'package:celest_core/_internal.dart'; +import 'package:checks/checks.dart'; +import 'package:corks_cedar/corks_cedar.dart'; +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart' as t; + +// Roles +const roleAdmin = EntityUid.of('Celest::Role', 'admin'); +const roleAuthenticated = EntityUid.of('Celest::Role', 'authenticated'); +const roleAnonymous = EntityUid.of('Celest::Role', 'anonymous'); + +// Users +const userAlice = EntityUid.of('Celest::User', 'alice'); +const userBob = EntityUid.of('Celest::User', 'bob'); +const userCharlie = EntityUid.of('Celest::User', 'charlie'); + +// Actions +const actionCreate = EntityUid.of('Celest::Action', 'create'); +const actionGet = EntityUid.of('Celest::Action', 'get'); +const actionUpdate = EntityUid.of('Celest::Action', 'update'); +const actionDelete = EntityUid.of('Celest::Action', 'delete'); +const actionList = EntityUid.of('Celest::Action', 'list'); +const actionInvoke = EntityUid.of('Celest::Action', 'invoke'); + +// APIs +const apiTest = EntityUid.of('Celest::Api', 'test'); + +// Functions +const functionAdmin = EntityUid.of('Celest::Function', 'test/admin'); +const functionAuthenticated = + EntityUid.of('Celest::Function', 'test/authenticated'); +const functionPublic = EntityUid.of('Celest::Function', 'test/public'); + +final defaultProject = ResolvedProject( + projectId: 'test', + environmentId: 'production', + sdkConfig: SdkConfiguration( + celest: Version(1, 0, 0), + dart: Sdk( + type: SdkType.dart, + version: Version(3, 5, 0), + ), + ), + apis: { + AuthenticationService.api.apiId: AuthenticationService.api, + UsersService.api.apiId: UsersService.api, + 'test': ResolvedApi( + apiId: 'test', + functions: { + 'admin': ResolvedCloudFunction( + apiId: 'test', + functionId: 'admin', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/test/admin'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.admin', + newId: 'test/admin', + values: { + SlotId.resource: functionAdmin, + }, + ), + ], + ), + ), + 'authenticated': ResolvedCloudFunction( + apiId: 'test', + functionId: 'authenticated', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/test/authenticated'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.authenticated', + newId: 'test/authenticated', + values: { + SlotId.resource: functionAuthenticated, + }, + ), + ], + ), + ), + 'public': ResolvedCloudFunction( + apiId: 'test', + functionId: 'public', + httpConfig: ResolvedHttpConfig( + route: ResolvedHttpRoute(path: '/test/public'), + ), + policySet: PolicySet( + templateLinks: const [ + TemplateLink( + templateId: 'cloud.functions.public', + newId: 'test/public', + values: { + SlotId.resource: functionPublic, + }, + ), + ], + ), + ), + }, + ), + }, +); + +typedef Route = (String method, String route); + +final class AuthorizationTester { + AuthorizationTester({ + ResolvedProject? project, + this.additionalEntities = const [], + this.persistData = false, + }) : project = project ?? defaultProject { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.message}'); + if (record.error case final error?) { + print(error); + } + if (record.stackTrace case final stackTrace?) { + print(stackTrace); + } + }); + } + + static const List defaultEntities = [ + Entity(uid: userAlice, parents: [roleAdmin]), + Entity(uid: userBob, parents: [roleAuthenticated]), + Entity(uid: userCharlie, parents: [roleAnonymous]), + ]; + + final ResolvedProject project; + final List additionalEntities; + final bool persistData; + + late CelestCloudAuth _authService; + + AuthenticationService get authenticationService => + _authService.authentication; + UsersService get usersService => _authService.users; + AuthDatabase get db => _authService.db; + AuthorizationMiddleware get middleware => _authService.middleware; + Authorizer get authorizer => _authService.authorizer; + CryptoKeyRepository get cryptoKeys => _authService.cryptoKeys; + CryptoKey get rootKey => _authService.cryptoKeys.rootKey; + Signer get signer => rootKey.signer; + CorksRepository get corks => _authService.corks; + + late CelestService _service; + Uri get address => Uri.http('localhost:${_service.port}'); + + CelestCloud cloud({Cork? cork}) => CelestCloud( + uri: address, + authenticator: Authenticator.static(token: cork?.toString()), + clientType: ClientType.HEADLESS, + ); + + Future _createEntities() async { + for (final entity in [ + ...defaultEntities, + for (final api in project.apis.values) ...[ + for (final function in api.functions.values) + Entity(uid: function.uid, parents: [api.uid]), + ], + ...additionalEntities, + ]) { + await db.createEntity(entity); + } + } + + Map get _targets { + return { + for (final api in project.apis.values) + if (api.apiId != AuthenticationService.api.apiId && + api.apiId != UsersService.api.apiId) + for (final function in api.functions.values) + function.httpConfig.route.path: _DummyTarget( + function.functionId, + [Middleware.shelf(middleware.call)], + ), + }; + } + + Otp? lastSentCode; + + void setUp() { + t.tearDown(() { + lastSentCode = null; + }); + + t.setUp(() async { + final initCompleter = Completer(); + _service = await serve( + targets: {}, + config: project, + port: 0, + setup: (context) async { + context.put(env.environment, 'local'); + context.put( + contextKeyEmailOtpProvider, + EmailOtpProvider((otp) async => lastSentCode = otp), + ); + context.put(ContextKey.project, project); + + AuthDatabase db; + if (persistData) { + final directory = context.fileSystem.currentDirectory + .childDirectory('.dart_tool') + .childDirectory('celest'); + if (directory.existsSync()) { + directory.deleteSync(recursive: true); + } + directory.createSync(); + db = AuthDatabase.localDir( + directory, + verbose: true, + ); + } else { + db = AuthDatabase.memory( + verbose: true, + ); + } + _authService = await CelestCloudAuth.test(db: db); + t.addTearDown(_authService.close); + + await _createEntities(); + for (final target in _targets.entries) { + target.value.apply(context.router, target.key); + } + context.router.mount('/v1alpha1/auth/', _authService.handler); + + initCompleter.complete(); + }, + ); + + await initCompleter.future; + t.addTearDown(_service.close); + }); + } + + Future httpTest( + Map res)> checks, { + Cork? cork, + Map? query, + Object? body, + }) async { + for (final MapEntry(key: (method, path), value: checkRes) + in checks.entries) { + final uri = address.resolve(path).replace(queryParameters: query); + print('$method $uri'); + final request = http.Request(method, uri) + ..headers.addAll({ + if (cork != null) 'Authorization': 'Bearer $cork', + }); + if (body != null) { + request.body = jsonEncode(body); + request.headers['Content-Type'] = 'application/json'; + } + final response = await context.httpClient.send(request); + final responseBody = await response.stream.bytesToString(); + print('${response.statusCode} $responseBody'); + checkRes(check(http.Response(responseBody, response.statusCode))); + } + } +} + +void Function(Subject) expectAll( + List)> checks, +) { + return (res) { + for (final check in checks) { + check(res); + } + }; +} + +void Function(Subject) expectStatus(int statusCode) { + return (res) => res.hasStatus(statusCode); +} + +void Function(Subject) expectBody(Map? o) { + return (res) => res.hasJsonBody(o); +} + +void Function(Subject) expectBodyHas( + List>)> conditions, +) { + return (res) { + res + .has((it) => jsonDecode(it.body), 'body') + .isA>() + .which((it) { + for (final condition in conditions) { + condition(it); + } + }); + }; +} + +extension ResponseChecks on Subject { + void hasStatus(int statusCode) { + has((res) => res.statusCode, 'statusCode').equals(statusCode); + } + + void hasJsonBody(Map? o) { + if (o == null) { + return has((res) => res.body, 'body').equals(''); + } + has((res) => jsonDecode(res.body), 'json') + .isA>() + .deepEquals(o); + } +} + +final class _DummyTarget extends CloudFunctionHttpTarget { + _DummyTarget(this.name, this.middlewares); + + @override + final String name; + + @override + String get method => 'POST'; + + @override + final List middlewares; + + @override + Future handle( + Map request, { + required Map> headers, + required Map> queryParameters, + }) { + return Future.value(Response.ok('')); + } +} + +typedef Otp = ({String to, String code}); diff --git a/services/celest_cloud_auth/test/users/users_service_test.dart b/services/celest_cloud_auth/test/users/users_service_test.dart new file mode 100644 index 00000000..e8211848 --- /dev/null +++ b/services/celest_cloud_auth/test/users/users_service_test.dart @@ -0,0 +1,710 @@ +import 'package:celest_cloud/celest_cloud.dart' as pb; +import 'package:celest_cloud_auth/src/context.dart'; +import 'package:celest_cloud_auth/src/model/interop.dart'; +import 'package:celest_cloud_auth/src/model/page_token.dart'; +import 'package:celest_cloud_auth/src/util/typeid.dart'; +import 'package:celest_core/celest_core.dart'; +import 'package:checks/checks.dart'; +import 'package:collection/collection.dart'; +import 'package:test/test.dart'; + +import '../tester.dart'; + +void main() { + final tester = AuthorizationTester(persistData: false); + + group('UsersService', () { + tester.setUp(); + + group('getUser', () { + final userId = typeId(); + final route = ('GET', '/v1alpha1/auth/users/$userId'); + + test('unauthenticated', () async { + await tester.httpTest({ + route: expectStatus(403), + }); + + final cloud = tester.cloud(); + await check(cloud.users.get('users/$userId')) + .throws(); + }); + + test('anonymous', () async { + final cork = await tester.corks.createUserCork( + user: User( + userId: userId, + roles: const [roleAnonymous], + ), + ); + + await tester.httpTest(cork: cork, { + // Passes authorization but fails authentication since we have no + // record of the user in the DB. + route: expectStatus(403), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.get('users/$userId')) + .throws(); + }); + + test('authenticated', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + route: expectAll([ + expectStatus(200), + expectBody({ + 'name': 'users/$userId', + 'userId': userId, + 'createTime': (Subject it) => it + .isA() + .has(DateTime.parse, 'DateTime') + .isLessOrEqual(DateTime.now()), + }), + ]), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.get('users/$userId')).completes( + (it) => it + ..has((it) => it.userId, 'userId').equals(userId) + ..has((it) => it.emails, 'emails').isEmpty(), + ); + }); + + test('admin', () async { + const email = 'admin@celest.dev'; + final user = await tester.db.createUser( + user: User( + userId: userId, + emails: const [Email(email: email)], + roles: const [roleAdmin], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + route: expectAll([ + expectStatus(200), + expectBody({ + 'name': 'users/$userId', + 'userId': userId, + 'createTime': (Subject it) => it + .isA() + .has(DateTime.parse, 'DateTime') + .isLessOrEqual(DateTime.now()), + 'emails': [ + { + 'email': 'admin@celest.dev', + 'isPrimary': false, + 'isVerified': false, + } + ], + }), + ]), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.get('users/$userId')).completes( + (it) => it + ..has((it) => it.userId, 'userId').equals(userId) + ..has((it) => it.emails, 'emails').isNotEmpty(), + ); + }); + }); + + group('listUsers', () { + const route = ('GET', '/v1alpha1/auth/users'); + const request = { + 'pageSize': '10', + }; + + group('pagination', () { + final userIds = List.generate(100, (_) => typeId()); + setUp(() async { + await Future.wait( + userIds.map( + (userId) => tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ), + ), + ); + }); + + test('defaults', () async { + final result = await tester.usersService.listUsers(); + check(result.users).length.equals(10); + check(result.hasNextPageToken()).isTrue(); + }); + + for (final pageSize in [1, 10, 25, 50, 100, 200]) { + test('pageSize=$pageSize', () async { + final seen = {}; + final numPages = userIds.length ~/ pageSize + 1; + + PageToken? pageToken; + for (var page = 1;; page++) { + context.logger.finest('page=$page, pageToken=$pageToken'); + final result = await tester.usersService.listUsers( + pageSize: pageSize, + pageToken: pageToken?.encode(), + ); + context.logger.finest('result=$result'); + for (final user in result.users) { + check( + because: 'We should not have seen ${user.userId} before', + seen.add(user.userId), + ).isTrue(); + } + if (page == numPages) { + check(result, because: 'Expect $page/$numPages to be the last') + .has((it) => it.hasNextPageToken(), 'hasNextPageToken') + .isFalse(); + check(result.users).length.equals(userIds.length % pageSize); + break; + } + check(result.users).length.equals(pageSize); + check(result) + .has((it) => it.hasNextPageToken(), 'hasNextPageToken') + .isTrue(); + pageToken = PageToken.parse(result.nextPageToken); + } + }); + } + }); + + group('order by', () { + const numUsers = 10; + var users = List.generate( + numUsers, + (i) => User( + userId: typeId(), + givenName: 'User $i', + familyName: '${numUsers - i}', + roles: const [roleAuthenticated], + ), + ); + late Map Function(List)> expectedByColumn; + + setUp(() async { + final createdUsers = []; + for (final user in users) { + final createdUser = await tester.db.createUser( + user: user, + ); + createdUsers.add(createdUser); + // Create users in a staggered fashion to ensure that the create + // time is different for each user. + await Future.delayed(const Duration(milliseconds: 10)); + } + users = createdUsers; + + expectedByColumn = { + 'create_time': (users) => + users.sortedBy((user) => user.createTime!), + 'given_name': (users) => users.sortedBy((user) => user.givenName!), + 'family_name': (users) => + users.sortedBy((user) => user.familyName!), + }; + }); + + const columns = ['create_time', 'given_name', 'family_name']; + for (final column in columns) { + test('ascending', () async { + final result = await tester.usersService.listUsers( + orderBy: column, + ); + check(result.users.map((user) => user.toModel())) + .deepEquals(expectedByColumn[column]!(users)); + }); + + test('descending', () async { + final result = await tester.usersService.listUsers( + orderBy: '-$column', + ); + check(result.users.map((user) => user.toModel())).deepEquals( + expectedByColumn[column]!(users).reversed, + ); + }); + } + }); + + group('auth', () { + test('unauthenticated', () async { + await tester.httpTest(query: request, { + route: expectStatus(403), + }); + }); + + test('anonymous', () async { + final cork = await tester.corks.createUserCork( + user: User( + userId: typeId(), + roles: const [roleAnonymous], + ), + ); + + await tester.httpTest(cork: cork, query: request, { + // Passes authorization but fails authentication since we have no + // record of the user in the DB. + route: expectStatus(403), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.list()).throws(); + }); + + test('authenticated', () async { + final user = await tester.db.createUser( + user: User( + userId: typeId(), + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, query: request, { + route: expectAll([ + // Only admins can list users + expectStatus(403), + ]), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.list()).throws(); + }); + + test('admin', () async { + final userId = typeId(); + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAdmin], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, query: request, { + route: expectAll([ + expectStatus(200), + expectBody({ + 'users': [ + { + 'name': 'users/$userId', + 'userId': userId, + 'createTime': (Subject it) => it + .isA() + .has(DateTime.parse, 'DateTime') + .isLessOrEqual(DateTime.now()), + }, + ], + }), + ]), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.list()).completes( + (it) => it + ..has((it) => it.users, 'users').length.equals(1) + ..has((it) => it.users.first.userId, 'userId').equals(userId), + ); + }); + }); + }); + + group('updateUser', () { + final userId = typeId(); + + test('can update fields', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final before = await tester.usersService.getUser(userId: userId); + check(before.givenName).isNull(); + check(before.familyName).isNull(); + check(before.updateTime).isNull(); + check(before).equals(user); + final updated = await tester.usersService.updateUser( + userId: userId, + givenName: 'John', + familyName: 'Doe', + ); + check(updated.givenName).equals('John'); + check(updated.familyName).equals('Doe'); + check(updated.updateTime).isNotNull(); + final after = await tester.usersService.getUser(userId: userId); + check(after).equals(updated); + }); + + test('can set field mask', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final before = await tester.usersService.getUser(userId: userId); + check(before.givenName).isNull(); + check(before.familyName).isNull(); + check(before.updateTime).isNull(); + check(before).equals(user); + final updated = await tester.usersService.updateUser( + userId: userId, + givenName: 'John', + familyName: 'Doe', + updateMask: ['given_name'], + ); + check(updated.givenName).equals('John'); + check(updated.familyName).isNull(); + check(updated.updateTime).isNotNull(); + final after = await tester.usersService.getUser(userId: userId); + check(after).equals(updated); + }); + + group('auth', () { + final selfRoute = ('PATCH', '/v1alpha1/auth/users/$userId'); + + final otherUserId = typeId(); + final otherRoute = ('PATCH', '/v1alpha1/auth/users/$otherUserId'); + + const request = { + 'givenName': 'John', + 'familyName': 'Doe', + }; + + test('unauthenticated', () async { + await tester.httpTest({ + selfRoute: expectStatus(403), + }); + + final cloud = tester.cloud(); + await check( + cloud.users.update( + user: pb.User( + name: 'users/$userId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).throws(); + }); + + test('anonymous', () async { + final cork = await tester.corks.createUserCork( + user: User( + userId: userId, + roles: const [roleAnonymous], + ), + ); + + await tester.httpTest(cork: cork, body: request, { + // Passes authorization but fails authentication since we have no + // record of the user in the DB. + selfRoute: expectStatus(403), + }); + + final cloud = tester.cloud(cork: cork); + await check( + cloud.users.update( + user: pb.User( + name: 'users/$userId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).throws(); + }); + + test('authenticated', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, body: request, { + // Can update self + selfRoute: expectStatus(200), + + // but not others + otherRoute: expectStatus(403), + }); + + final cloud = tester.cloud(cork: cork); + await check( + cloud.users.update( + user: pb.User( + name: 'users/$userId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).completes( + (it) => it + ..has((it) => it.givenName, 'givenName').equals('John') + ..has((it) => it.familyName, 'familyName').equals('Doe'), + ); + + await check( + cloud.users.update( + user: pb.User( + name: 'users/$otherUserId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).throws(); + }); + + test('admin', () async { + final adminUser = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAdmin], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: adminUser); + + await tester.httpTest(cork: cork, body: request, { + // Can update self + selfRoute: expectStatus(200), + + // and others + otherRoute: expectStatus(200), + }); + + final cloud = tester.cloud(cork: cork); + await check( + cloud.users.update( + user: pb.User( + name: 'users/$userId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).completes( + (it) => it + ..has((it) => it.givenName, 'givenName').equals('John') + ..has((it) => it.familyName, 'familyName').equals('Doe'), + ); + + await check( + cloud.users.update( + user: pb.User( + name: 'users/$otherUserId', + givenName: 'John', + familyName: 'Doe', + ), + updateMask: pb.FieldMask(paths: ['given_name', 'family_name']), + ), + ).completes( + (it) => it + ..has((it) => it.givenName, 'givenName').equals('John') + ..has((it) => it.familyName, 'familyName').equals('Doe'), + ); + }); + }); + }); + + group('deleteUser', () { + final userId = typeId(); + + setUp(() async { + await tester.db.usersDrift.deleteUser(userId: userId); + }); + + test('can delete user', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + final before = await tester.usersService.getUser(userId: userId); + check(before).equals(user); + await tester.usersService.deleteUser(userId: userId); + await check(tester.usersService.getUser(userId: userId)) + .throws((it) => it.isA()); + }); + + group('auth', () { + final selfRoute = ('DELETE', '/v1alpha1/auth/users/$userId'); + + final otherUserId = typeId(); + final otherRoute = ('DELETE', '/v1alpha1/auth/users/$otherUserId'); + + setUp(() async { + await tester.db.usersDrift.deleteUser(userId: otherUserId); + }); + + test('unauthenticated', () async { + await tester.httpTest({ + selfRoute: expectStatus(403), + }); + + final cloud = tester.cloud(); + await check(cloud.users.delete('users/$userId')) + .throws(); + }); + + test('anonymous', () async { + final cork = await tester.corks.createUserCork( + user: User( + userId: userId, + roles: const [roleAnonymous], + ), + ); + + await tester.httpTest(cork: cork, { + selfRoute: expectStatus(403), + }); + + final cloud = tester.cloud(cork: cork); + await check(cloud.users.delete('users/$userId')) + .throws(); + }); + + test('authenticated', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + await tester.httpTest(cork: cork, { + // Cannot delete others + otherRoute: expectStatus(403), + + // and can delete self + selfRoute: expectStatus(200), + }); + + // and then nothing more + await tester.httpTest({ + selfRoute: expectStatus(403), + }); + }); + + test('authenticated (cloud)', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAuthenticated], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + + final cloud = tester.cloud(cork: cork); + // Cannot delete others + await check(cloud.users.delete('users/$otherUserId')) + .throws(); + + // and can delete self + await check(cloud.users.delete('users/$userId')).completes(); + + // and then nothing more + await check(cloud.users.delete('users/$userId')) + .throws(); + }); + + test('admin', () async { + final adminUser = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAdmin], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: adminUser); + + await tester.httpTest(cork: cork, { + // Can delete others + otherRoute: expectStatus(200), + + // and can delete self + selfRoute: expectStatus(200), + }); + + // and then nothing more + await tester.httpTest({ + selfRoute: expectStatus(403), + }); + }); + + test('admin (cloud)', () async { + final user = await tester.db.createUser( + user: User( + userId: userId, + roles: const [roleAdmin], + ), + ); + await tester.db.createUser( + user: User( + userId: otherUserId, + roles: const [roleAuthenticated], + ), + ); + final cork = await tester.corks.createUserCork(user: user); + + final cloud = tester.cloud(cork: cork); + + // Can delete others + await check(cloud.users.delete('users/$otherUserId')).completes(); + + // and can delete self + await check(cloud.users.delete('users/$userId')).completes(); + + // and then nothing more + await check(cloud.users.delete('users/$userId')) + .throws(); + }); + }); + }); + }); +} diff --git a/services/celest_cloud_auth/tool/generate_policy_set.dart b/services/celest_cloud_auth/tool/generate_policy_set.dart new file mode 100644 index 00000000..c9aeba73 --- /dev/null +++ b/services/celest_cloud_auth/tool/generate_policy_set.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; + +final policyDir = Directory.fromUri( + Directory.current.uri.resolve('lib/src/authorization/cedar'), +); + +Future main() async { + final policyFiles = await policyDir.list(recursive: true).toList(); + final policies = policyFiles + .whereType() + .where((f) => p.extension(f.path) == '.cedar'); + final policyOutput = StringBuffer(''' +/// The core policy set, in Cedar IDL. +const corePolicySetCedar = \'\'\' +'''); + for (final policy in policies) { + final policyData = await policy.readAsString(); + final policyFilename = p.basename(policy.path); + policyOutput + ..writeln('// ${'-' * (policyFilename.length + 6)} //') + ..writeln('// -- $policyFilename -- //') + ..writeln('// ${'-' * (policyFilename.length + 6)} //') + ..writeln() + ..writeln(policyData.trim()); + } + policyOutput.write('\'\'\';'); + final output = ''' +// This file is generated. To update, run `dart tool/generate_policy_set.dart`. +library; + +import 'package:cedar/cedar.dart'; + +/// The core policy set. +/// +/// Included policies: +${policies.map((f) => p.basename(f.path)).map((p) => '/// - $p').join('\n')} +final corePolicySet = PolicySet.parse(corePolicySetCedar); + +$policyOutput +'''; + await File.fromUri( + Directory.current.uri.resolve('lib/src/authorization/policy_set.g.dart'), + ).writeAsString(output); + print('Generated lib/src/policies.dart'); +} diff --git a/services/celest_cloud_auth/tool/render_email.dart b/services/celest_cloud_auth/tool/render_email.dart new file mode 100644 index 00000000..9bad3b0d --- /dev/null +++ b/services/celest_cloud_auth/tool/render_email.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:async/async.dart'; +import 'package:celest_cloud_auth/src/email/templates/verification_code.dart'; + +Future main() async { + final server = await HttpServer.bind( + InternetAddress.anyIPv4, + 9999, + ); + print('Listening on http://localhost:${server.port}'); + unawaited(_handleRequests(server)); + + await StreamGroup.merge([ + ProcessSignal.sigint.watch(), + ProcessSignal.sigterm.watch(), + ]).first; + await server.close(force: true); +} + +Future _handleRequests(HttpServer server) async { + await for (final request in server) { + print('${request.method} ${request.uri}'); + switch (request.method) { + case 'GET': + final url = request.uri.pathSegments.lastOrNull; + final template = templates[url]; + if (template == null) { + print('Not found: $url'); + request.response.statusCode = HttpStatus.notFound; + await request.response.close(); + break; + } + final email = template.$1.render(template.$2); + request.response.headers.contentType = ContentType.html; + request.response.write(email); + await request.response.flush(); + await request.response.close(); + } + } +} + +final templates = { + 'VerificationCode': ( + VerificationCodeEmail(), + const VerificationCodeEmailParams( + email: 'test@celest.dev', + code: '123456', + organizationName: 'Celest', + name: 'Test User', + logoUrl: _celestLogo, + ), + ), +}; + +const _celestLogo = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfoCBMQMC8sriglAAAlwUlEQVR42u2dd5xcZdXHv+fOZhPSewECCSkk0vG1gI2iiAKC0pEioKivoqIiltdXRLGhYkWpGhKKgIBAqEGqCK8oKDU9ISTZsLvpIdnszpz3j3Pv7mSyyc5m79x7Z/d8+cyHzczuLWee333aKeA4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4juM4jtOtkbQvwOkaI65+pSLHrf/U1LRvzSkDF3AV0YFYBegD9AcGAoPCn3cCaop+T4FNwJvAWmANsB7YALRs7wQu6uzhAs442xHtQGBXYBIwNfz/7sBIYAgm3N5ALRAU/Z0CzeFrEybeemA5sACYDbwKLARWhL+3BS7k7OACziDbEG0vYDfgrcC7gQOBCcCw8LM4WY8J+iXgaeDv4c+Npb/oYk4XF3CGaEe4AowDDgE+BLwN63VrOnPcGFiH9cyPAA8Az2JD7y1wMSePCzgDtCPcfsA7geOBI4DxbDkMTpP1wPPAHcDdwDxsWN6KCzk5XMAp0o5whwAfBM4A3gMMSPsaO2Ah8BdgBvAcUIg+cBEngws4BdoR7gDgGOAzwDuwhadqog64HbgGE3IrLuTK4gJOmBLx1gCHAV8K/9877evrIkuB6cBVWO/cigu5MriAE6KdXndPTLinYnu23YkXgZ8Bt2D7zYCLuBK4gBOgRLy9MdF+HRNxd6UJ+DPwA2wLCnARx40LuMKUiHd34H+A0zGvqZ7AbOAS4FaKnEJcyPHgAq4gJeI9DPgRtpfb09gA/C68/1ZnEBdx13EBV4B2FqrOxnqh0WlfW8rcBVwIzInecBF3DRdwzJSItx9wEfAVoG/a15YR/gGcDzwTveEi3nFcwDFSIt4hwKXAeUAu7WvLGHOAzwMPRW+4iHcMF3BMlIh3OPBzzKPKaZ/FwGeB+6I3XMSdJyv+td2JYcDluHg7Ynfg98CR0RuVSk7QnfEeOAaKGt5ArOc9N+1rqiIWAJ8Anoje8J64fLwH7iJF4u0N/C+24uyUzx7Ab4F9oze8Jy6fpONKuxVFDU2wRZnzSf+h2ASsxvZbG7G43Q3h+wG2Mt4fG+oPA4aG/05zNLYPNu04E/OndsrEh9A7SEkv8TEsEmdICpeyCXgNi9F9FnNbXAQ0YH7Im7FcV1GoXw2WwaMPMBjYGUvHcwCW7WMqJuo0uA57CL4JPpQuBxfwDlIk4H0xp/0k/ZoLWCD9Q8D9mHjr6CApXRkMBCYDhwIfBv4L652TohnbN788esNFvH1cwDtAkXiHANOwWN4kaMYcIG7Atl8Wt/dLnWn025lvDsBik08GjiY5L7IVwCnAo529l56IC7iTlDT4b2EukpWe9xaw5HJXAvcAK4s/jLORtyPoHDbK+BQm5iSG148DJ2FidhFvBxdwJylq4O8BbsPSuFaSxcBvsJ6+vviDSjfsEjHnwnu+EEv7U2nvsu9jq/qaxL1WKy7gTlDUoAcDN1HkhFAB8sCdWEN+vviDJBtzOz3yIKw3/jIwpoKnXg2cCMxK+p6rCd9G2jFOA95fweOvBi7Det610ZtpNOLonEVCXgP8FAtK+DE2T+4sG8LjrCx6rQ3f34x1LAG2zSWUZL102vAeuEyKGvAe2Dy0UmpajA1Tb6Oo4WalByrpkccDPwFO2M6fbMZWyOcCL2DbXPOwxPErMdE2YyOOwrYOkpX7zxreA3eec6iceF8B/ptwBRay13DrPzW1WMQLsUyaqzD30Wgxb0N4L0+ErxeAZRTlx+roHE55eA9cBkUNdi/gXqzESdy8jM0tn4reyHJDLumJBwDfA96H7U0/gKWXXbmtv8/yvVUTLuAyKGqsP8IcDeJmPubQ/2T0RjU08BIR74Q5fdS397vVcD/ViAu4A4oa6UTM62lCzKeoBz6JpZuxN6qosW8v8KCa7qNa8Tlw+RxH/OLdjG0TVaV4q/F6uxtpR85kmqLeZShWaCxubgSujv7hYnA6iwu4PA4G9ov5mC9gve9GcPE6O4YPoTtGsGCFnWI85iZs/3Q+9Ezx7mjQfk+01fZwAYdsp0GNwbZH4uQ+rJpft6dMoUaeVwFbLqwWil7bPV5PFXaPXYUecc2roNv00OuHufGNxhz3/4f4Sn6uwubTj0D3a3hl2HU4lkRgV2AXYBRm64FY7uw+WLssYA4hb4Y2a8CydSwBXse8u1ZjHlxbIkL9J6ekbYpE6HE98IhrXm37h4i9CoXBmIvk/rRlpdgda1j9iTfy5h6K9nu7C1vZNQggnx+CZfvYDzgQmEKbXfvRebsqtnK/GnPFfBX4F5aJ5GUKhTfI5bT4erq7kHtED7xF42pjJCbWw4F3YQ1taIVtsg7V4xF5CNWq7323YdedsfpPhwEHYfvng6msXTdgbp1PYV5gT2Oum1vQHcXcrQXcTgPrh/UEHwE+gKWPiXNxqiMexPJnbahmAbdj14HA24FjMeFOIL1i5ZuxwImZ2DrDc+F7rXQnIXdLAbfTwEZisbunYr1CGgW1FQtU+D1UZyNqx65jsXQ7J2P5s/qlfY0lNGLxxH/EAkQ2FX9Yjd9BKd1KwO00sFFYj3c2lnUxzTn/61gM8WyorsbTjl0nYHmrTsPWC7LejtZjQ+vfYul6Whe+qul7aI+sG75shl87u+2mVAdjMaqfxobMWXBYuQNr9JurpdEU2xRAVHfBSsacjU0/qo1VWCaVX6nIFjfXcG6SSUXjo+pXoUsaWU5UDwe+iqVGzdL9PUHJXCzLlNi1v6ieAHwBG8lUK0OwaczhovoTFbmJ0BNu+LWzq1LEVdsDD79uNlK03ai2PXEBcBa26pkl1gNHAY+LaqaHbSOKhJsDWmxx6uvh9ce1F54FmrDe+Hti9ZkAqK8yEVelgIdf19bIggKBCkcBF6sNl7PIPGxE8HqWn/LFdhVlABbm+BU1h4tuidg+8tfyAQ8HYYfQcE52v6NSsjA37BTFjQwYUgi4WIXpKhyIQEZfiwQas/y0LLHrFBWuUeEnKuySAftV7KXCgSpMD5RzCB1LSmyRabI0R+yQYdfNac3yJuhkLEPGcWR/JLG4IGzK6kVGdt1MLb1pOhLLNrlvFw9bTYwBfontWvxckaZh180BoPGcbK/VVY2Ah4cGDZ1k36PIL8jukLkEXQqiWcuOGtkUoAA1tWw+V5FLqHyy+izSH7gYGKg2L35TQhs1ZFjEVTGEHvYHa2jNNaBwrMD1VI14AaS+68eIH7UhJAI7BfBNrDh5TxRvRC3w1QC+E1hQBZrVYVNI5nvgSLzrBkD/9ZyOJRUflXG7FlPAVqEzszgS2TSkf0H4rijni5Ud7enUAF8qCOuxKVrzsD/MofHsbPbCmRbw0LChDQZYz5nAz1UYlvZ1dRLFnO0zg9K6aNAfywryeZWK1zqqJmqxrbPGfnDFBqwtrsygiDM7hB72hzmtUd6r4OPY8K7axAuml8xMfqPeV22I+L/A56l8obJqpC9wyfqi0rElI5dMkEkBD/3jXABUhAIcIzZsrkbxgtk4yYinbRLZNaeaE3N6+SIu3u0xTGxFfh/B2mPWyNwQuli8WDK5X5BccelyyGO1fArhz1EqmBran0MGJFvlvl0iuwLkg+B0Uf0G6XlWNWMZNl4HXsNid+uxgmfFGTZ2wmK0R2HOJGOxeONBJLd1OFVFLgXOBFYP/eNcVn5iUkpm25pMCXj4tFC8tuEyAbhcRfZI8ZLWY2lc5mExpouxotOrMR/aN7G41z5YTOxwrJFNwALZdwdGkLJr5/Bpc8kF0JIHFXkv8H0VGZDwZawDXgT+BvwdKyVTB6wX1Zbt/WHjsGEMXbmyD2bH3bDMKe8B3okVWKv0KOIo4HNrm7h0YG+zZ8NZ2RBxpgQcIcJAlB9gfriJotYT/BOLH30aE24j5jtbLjlM0GMxh4jXkr6PUloKgDBWlB9i+agSQe3hdzeWvP557OG35e+EQ9P2erahf5zL0JUrwWJ568LX/wHXhvdxKHCSmKArNdIJgPMH9eFxlCeSsl05ZGZQP2zaPAQlR17y5L4JfJfk5mctWJ7m24F7FV5RtSiV9thWQ+uINIZekV2x4fIvgM8mdOrZ2H79zfVnsmDYtHjs0Z6dA6Ef8F7gc1imlUpNDe7DkkKsUYTGsyZW6DTlkxkBj5o2jzygcISgN5LMolUeeAZ7ms/Ehsco1mPlpGuiK21saQi4/7R59EFR5FRBr8FWVytJA/AH4ErCvNca7lupxmuDoX+cS20uHF0YA4CTgK9RmXjlFkU+H6BXtiDUAPUpizgTAh5+/bxoo2VntcLWByVw2pewDA23CNpY/EFW5jddpciu4xTuJP7qEqU8Bny3QPBojnzr1lml7RmtnUQoshfwA2wLKO42/qJYGqHFCDSc2cMFPPz6+YCiARLk+T7m0ldJ1gPT1PaVFxRv0GZhSBQXrXYVJCjwY+DCCp5uE3CVwg/V5qhA8vYcNm1e689iW0AXY1lZ4vYw+zbK9zWwMzWcGXfNu/LJzCKWFHi3Cp+q8GlmA/8rcAdKcws2ye5Owi1FlHeocFYFT7ESE8qVKJsDbH8tDZs2njUxnPPbP1X4GuYF9xXibetnINxIUSKAtEhVwCOnz6egANIP+DK25VIpHgS9EPiPnRJ6afpzmEpQZNdaLIVMpQIU6kAvUGm5WbTGxnMav3hHTp+/zc/eOGPL3q/xrImMmDYvCkLYCHwPGATymRgvaTJwoqA/zqfsY5fqEHr49PlRXPVJCtMII0BipoAyXeHroK3Du7TnLpVkRNjgBQ5VW1kfXIHT1KlyfiCF2wqhWuK26YgS4e4ENNlwWAu2c9BKfYmQh18fDacFYITA9QhHxnh5zwt8CKjTds6fFKkJuOjLGQLcIfEXEANbZb5C4NsFZU30sExzzlJphs+YH+UKqwGuAc6qwJe8Cvh8c4EbawJbXY7bpiXiHQocARyCeWJtwhYhZ2IpcQpgoX8Np295HcOmzScIQKwKx23AuDiuT+0BcjYwA9ITcGpD6EG9YY25RhwDHFyBkYhiWxnfBNaLxb1uNeTqbuRoLeW3H9ZDxB1J0QRc0gI31ZgwqI9ZvKNmzGdTAfoEUFDeBvwQ2+ctXow6EdvT/o3CLwJY355jfy5ovf9/KlyOLV7G4V9Qg+Ucv5XOOfnESirBDCNnLGDtZhBhEMInEHpVIN/RTVhVwfVROFB3Fy/hfbbYvuuJCCNjt6v16r+rCXVRKZvWmvD2QbgW4fBttJFRCN8V4Rsi1CjWtoqJri8U8QyEJ2O0x8EIeyL20EmD1KKRQhscEsBBUTRAjK9HBS4SYVV0vrSGOEkycsYCBKgN2DWAYypg18dEuFTEepxK2jSA3gLfCGCfDq4pCCwk8lBbQ9t6wlB/xgREIRBWBvC7AJpisseoAA5NM6Qv8SH0qBsWhMEKUouV5oh74WqRoF/DIl16hHAjeveGpiYBW0+I2xPpDYHvAMvD/eWKEYpwb2zeWw4DgVMUeVjQQrvHDFoXfB5U5FmsImUcHI7Vu0plGJ3mw2MvbFEiTjZh2wb/AEnfSyVhNtu0pAbzFIrz4azAb9e17PQYmBBKF4sqwD7Y4lW57Cfo4G19WH/6BDZZ7fBVwJ9jvM79sQgpRt2Q/LZw4j2wqFoBaPgw8e9P3grcaI63sKLyjSwzjLxxYeh0zHgRDo758M8AVw7otRFNwK5hcFJnazX3pYMght40gS1mPogVCB8Tw+WOxhYM56YR8J9oDzzmxoUUggAVGUS4Qhoji4DLCEtIvnF6mmHEyRMtq6olQYgzXHATljN5hWqidq2ndUG9LNZRUj60lKJrn4eFjMZBL2yLqlMXGxeJ9sBFydT2UZF9Yj781YHqCwUR6+V7GAVARAKFwzTeB/PDWDxvFJZYccKe7N+YiEeV+Wf/KsCajm68EASIahPwV2wUGIet9hXonUthHpyogIuGz+8TW3iIi/8A06Ox14qP96zed9RNi6KH1qgA3hbjoTdi20YbCiRn11BRr2APjk+W8ScNwI0B21jBKkK0dZ16mlgKn5OxzB5daY97YEP+5YkYaGtbJUP4ZN0Jy54QJ9OBJQK8cdr4JG8pExTNvPbC0vjExdNYT5UoYT/fjCWUe7qDX98E/EgsVU+HFLWPlQrXYc4YxwFXAAt38JJHklLetsQFrCJjVWTv8Oc4XvNV5A6VpAZ42UOk1bZvV5G+MdlVVeRWFVkLyT4Yix5I84Czw+vY0M41zlORC4BfazgFLec6V5w2vsgvhQ3AI9he8hEq8iUVeVJFNnbCVv1UZGcVYfTNixKzEyQ4hB5986Lo0boX8a4+z1wnufn9NU9dD+x9AbBJby8NF1NiYgkwK43bqTttPKNvXIjaesarwCeAg8O8V2Oxof0LCrNy+fy8QhC0/l1nzhEx+saFoRWZJ6q/VJFpAu/Gsnu8n45Xq3tjPtqJk+w2kj3yDiC+AOsNwN0DyPfY3hda6/cMB6bGeNi/SzikXH5q8g/GutPGM+qmRdG0601gVgFmDQR5HXRQ+Hv5nK2/rzh1XJfPVcRqhXtq4P4CTFXhWOCj2N70ttpuJUNht0nC+8DSC3TvGA/4MvAcZDRDfQLsfPNiCvb4Gke8vcCjCi19NnX9QDtKJMoxReLaADqk6HeWd0G47Z2r+HxhyOILorygwlVYBsyTscCK0pxtQ8o5T9wkJuDAut9BSqx5np8MoDEPLD81zrWb6qGmBZprBGAK8a3sN2KpW9nUJ/2xTVwi3ZHzFc1p3wD+BNwJsr/Y4tfRWP7vWmBAnuTLXCTdcY0QGB1TMEiLwFMWaZR+I0uL5ppWe+wl4SZdDK+F4Yu6U8alfYupUnfKOOpOGYeElhWRJhGe2Sx6kQjnCTwV2qxPTqKNzORIrAcO52k7Y6k/46AeC+oml/UirpXEvEZrsR44Ll6inQTsPZFdblnc9g+BgtIb2K8WOV7haIQt0pAk3RQTE3D4aBpNfIW+XkNZBrDslJ45fAYIzK6DNd793xcFtNBDBzY739JWSCMygaAjgUMD4SS1aK/SOXCe2HMndExyArb/jaZzDurbYyFWhNmxaUm5LocdsRnL3tnj2PWW18hhHiQhNcBUQbdYhd5GA34zjWtORMC73PpaGCgTa7WFxUC+57pvbGHXXYlvAWstYS2n5SfvlvYtVpxdbt2yt20GRBmsRfvAipQTtbQujetPugce1LWjbMGypK49y4R2HY85E8RBI7bi2q0pFm6IABPEKhGeECbB68x0b1Unfjc2ktsHtvj6OAtdrwJYemLPnf8CkYLj9LRYjvXC3ZZdblvS9g/VflgAyIlYiOv4HZjkFYjqaiU8IExEwKE3TU7iK6zVgnlh9WhCu9ZIvAtYy1Dd2PXDZJNdb1sSTboGK3wMkZOAg7oYHbeZMBJp2YljE72fRAQctPtjl4gSTfZoQmP2Jd4A/qWIFPL5fNq3VzHCDvYkgd8RT5tcSwqhhMR08Z0hLtH14I3frRhCvMEhSwBqckn7FCVKLZYwL672X0c4hE6aJAWcJ76l9hyVr3NbLYykc8nftkcLKfUkSTD2z69HP04i3hK280nJ8SWRIbS5oaF0kLOoM4ck3hXtqiS0665A/5gOuYmwPOiS4+MclWcEbbXZMcQb+PE80KwppHJKpAferBo52a6NMSv+aAR2u31p4kbLArvdvjSywziEmphsug6hoTtOUHa7fam1dmEMwskxtsMm4kuQ12kS6YFr21y8G2M87FhBRBNfuM8GRQEccUZ3NWL1frsVJQ/5E4B9Yzz86wgvQjqZyBPpgZccvyti/60QRIVY/hsH9O156duN0Aa9BRkfkz0RpE6Qdd3OptJqsz0E+bQgQYw2+6cgywRJZdqRdGL3Oiz1ZhzlVMZhCzgLx92xlEUf3SXhW8kEg4h3D3gJZaxTjLuj/WlLFr+DcXcspQAUhJpA+SKW0ikuFEs71JwnnW23xFahw3DK5SKskzBwtYuvUSJMjhK69SR2v3NZZIOdRRgTkz0RYWE5uQFbalqf+1OAXRT7Dna/M1verbvfuaw1lXEOjhPh7BhthQivIzyOQC7xUH4jOQHbyVYI1MdkvZ0QeYdgWSl6EipENpiMyKBY7Al5gbmI8NrHtt+T5kInDxU5RUV+iUhrPqjd/pINEUfXUQgCBPZB5BKQAXGqV+CxAOYH0KHNKkViAg7Tb65WkUUxHvY9BZF+zamVKU+HFlqndW8lrmmQyDoVKavIrQIFkRyWRO944DKFwSLJp5Rpj/F/WUYNYbpd22a7HJga89R+k4rcpiItaVYCSVDAEAhNCC/HuIS/fyBMCQTG3dVt/Q+2ohZA6I/wjhhtuQxhSTmNPLDvcjDCpPBvzwyESwMYoCKMu2t5at/HuLuWoyIUrIccEwi/wgqEx2Wn6PVsNHyO0tqmQXJDaG094b8CyMdUYHm4wId60gx43F3Loza0ZwB7xVi8+9UAGjtqEEXnHxvYi8A6u08XRC4jjPkOSP6hWnK+scAVAh+tQKHzQgAzBFbtOQwWH5dKSmggQQEvPm7naBj9goo0xFiZ4VgN52B79IBeuAXA7vuDKjI8Rjs+qyLNHS0I9qF1OrSvigwt+vsc8GngSmACWOPa467lFf9eonMIrUP4A4DrVeS4GO1T/HpORf6CCHPj9GzYAdLo+xcLzI5xNLOfwAdhi9XRbsn4u+uirOIjBD4aow3fJEwj2xGbgbEtLQi8U8KUXCWv48XSrx6ZKxSCqNGPv7uuYjaJzhGo9lI4VeBPAofEP2oGMZ/+ayV0OV14bHq9LyQs4JwZYJ3A32I0aC+BcwQG1+Tz7FGhhpIFcm33fGT44IrLhgsCeCkAFn2k4+wxS2tqRoQC3tbx3grckA+CHxBWrweYeHcdE2L6fiYUHSscM0xRkd8AVwtMqpB4EfhbALdkZdqWeH1gbKPxEeALQL+YDv0u4BiF6b1qYcI9dcw/OpVicRVjwj115BUQGQKcS3zlaQCeAl3RKoXtXEOYqXIfYHIHxxwKXIRlubgK+LMG1KF2nIhyv6fiv4nQAMizu8JpwLkqMiFGm7THeuDnQKOolvWwqzSJjznDJvIc8CoSWzGuWoHzgVn5ZpZ3N8eOPWauQAoFxFZWTwDeFePGxWaUB6B8t/JAOELLf/juK/Ar4JMotwL3YFkvm6J7KweF4sLtfYG9pMCxIhyv8ebE3t5F3KxwXyLnKpNEBbzgI2OYOLOOgtIgVvk9zmp6bwM+mxf5Tg50wswVzD8qrkyr6SLYwpFY7qsvYGl04mIOwt8BCmXsIQXCSOCDnTx/AOwfvs7H5tuPAf8AFgCrCrAxFw7SlLaxQMFWuPtiPfok4B3AIVjbGQYdjRti4xWEywQ2F9TachZIvAdWGwaicBdwHjA4xsN/JoBHgb8KMHHmCuZVuYgnzlxB6KpYA3wZiLM4HMD9GgTLg3yehcdsezg7ceYKK8BrJT7f0oXzjQY+Er7WYQkElgSwVK3axnpgo5poB4j5u+8CjFWR0cQ37eoMG4DvAXNENUqmnwlSWbYtGkY/JfDhGA89AjP0XMLUMNXMxHB4GS6enIjVyY2TVQp3SKFQ1nUE5kNyIqEvSQwMCF8dzadTReH3Bbg10m2W1lcS30aaf/RoNmJbFwI3E25txsjBwHcJn9QT732DSfdWX5rjSfe+Efkogw0XLyG+zBsRjwn8Kxqib5O2z/YHDk/bNgkzU+BHubCdZkm8kFIPXJTM6gEVeR74r5hPcQbwmsD3gZakJklxMem+8IFj043d1VY+J3blmO2wSWC6wiZRZcE2phqT7nsDVVAJBPQMrJB4T+E5Ub0QaIBs1qBO5ZrmHTUKzeUQkTcEZgThjkCMr5oALhT4nIjdY6soMs7E++tbfxZhjMAvAnhvBdwB/w48XM6zTYAA3SeojFtiVl/zBb4g8MqmPha+nsX1lPRclwoFEBC4DZvb7R/zGfpiPbDm4bc5kfzkUBxzjhzRpQNXisn317euOKvqGOA3CMdV4FRNwFUCa94swNJtNMzJ99dTUEWFILAFx+xF7FeG17HV8idB6N3UlEnxQoqjgvkfHhmuzshSRK5EJB9nrGb46o/ID3IiF0lRFpDJRb1cVoiuKdzp3AOR3yPysQrYBET+CswE6NtBFywiBCIHIXJSha4la6/Xgf9WkftaF60+HGfa7XhJd1jfmqySWwWerJDrWz+Bi4GfUpQAPSsinnx/fem1HIRFunykQvZYK/ArEVmHKnM7aJxiGv+imP91pdwTs/KaL3CeqN4dibcj+6RNqgKe+6FW4zQCP4OK1fvtBXwOmAG8LQgCRIQ9H2hgzwcaUrv/PR9oMO8qW+XtDZwN3ES8ScdL+RPmRAPlxbEeie3Zdnf+CZwJ3BfZpah9ZpYshe/cD9woIudV8BwfACar6k+B6SqsAZj8oIl4zhHJLLBOfrCB/uthffiYF2WKiFwInEq8FRxLmauqlxPWsJ5b3lrAfOBpEXlfIsZJHlXVu4GvUVTYvEzbpE4mNlgm318f9UKTgDuIN3Nge7QADwE/LwiPBdAc0ObC90oFhDw1fEjkwpOH1dlGiHIaNjqYVOF73gycr6pXBUFAoVDocDFv4r0rCEcruwDfBs4inoyiWWEdcAVwmao25vN5crlcZhc52yMTAo6GsWpXcypwNcm4zK0B7hS4LlB9RoOgqfjDVz8wrMsnmPLQlhHfBRGCQmGUWvTUucDbSWYqcwPKeRLWp5r9wY4fUiXTi94qfBz4BvHvSafBC8AlotxJkTNROXbJEpkQMISNRUChVwA/Ab6U4OlXhyuzfwKeyMHyKMtvcXzO7DIEvWeRYEuM2weLmjkKOB7VfUkuB9yLWPK5Oaqda6TFIrYtLvYSCxM8nuosMLcWuEHhZyitSfyqTbgRmREw2NwwsKH0KOAGknfb2wzMwSJlHlb4t8Lyfjk2NhXKP4gAG/PkegUMEeut3hney9uJtxRoOawEzla4qzaXo6mlZYfm+pMfbEAJnRxE+igcDXwRW3DLQjLKjmjGvtfLReShQqHQ3BJeeFJrH5UgUwIuGW4eiK3IpuXo3gwsA+aI8BIwR+A1zK1uLTYUjTroWsxPeQgwRmGCKlOwtKvjiTfiqrP38E2UnxEmbO/qtKDkOxqOBTeci+WhyqK3YQvwDDYt+wslZUDjmCalSaYEDNZAIt95sTKQ12JRRllgMybcTZg4IgHnsG2gvthQOSsN+QrgqwobVeNrrFNKpwnCaLGtpo9jfu1ZGFpvAJ7G/L3vQWksng5Vu3AjMidggKmzrIFYVQw5F0vMPSDt66oybgM+CzQISl7Lm8N3hikPNdKiUBs+rhQZhKU3Oh44FMuHleTwOg8sxPa57wCeEnQdQP8aWNMcvw3SJpMCBpg6y6pcFpQgCLhALM63knuk3YkHVfkkYUz0K+8fWtGTvWVWIznytLS5FQRY8bl3IxwmNvffjcrsLKwHFiv8H8rDwN+AxYSjo7UMZBBrePn93Uu4EZkVMJiIw3IdNQXLRnExLuKOmAV8WmFBXk1JlRZwMdGDN6J3QNCsjMJW4A/AglYmAztj1RX7Up5DUQs2fVkNLMUWG/8N/AuYXRvwxqY8Wy01JnnvaZBpAe/1cFtjCFPKXAB8h3TSqlQDDwGfARZECeBeOjydBlz83ZUQqMgALJ/VaGxVfhS20DeALdcPCtiC4SrgjfC1AmgQ1fXh51uR1j2nQaYFDNYQomwRCrkAPiXCpViSM6eN21W5AHgtyuCYpYZc8jDu8vGKC4pl6T6TJvMCjnjLX1chwKD+sHYDJ2DOHuPTvq4M0AJcA3xblQbUPNpePmxI2tflJEDVCBhg70dWAaB22QcBP5PKRu5knVUKPwb9deQiCfDioS7enkJVCRjaRAyCCLthC1sfJ75MidXCS8C3ROSuQkE12pJ28fYsqk7AAHs/sppAWr0odsJS8lwE7J72tSXAZuDPWJbKV6M3CwovHTo47WtzEqYqBQywz6Ori+5CUDhQ4FuYj2537Y3nA5cpzBDVDdGbLxwyOO3rclKiagUc0SpkSz/ZX+Fk4EtI7BUM0mQtyi3A5S3wco62WkEu3p5N1QsYYN/H1pTe1TjMwf5MispbViGbgL8Cv0Z4mIJl0gD4z/sGpX1tTgboFgKO2PfxNiH366VsaA72FuEcrKLf2LSvrxNsBB5XuFqVBwTdIlfYf97r4nWMbiVgaBOxpccRamuQ5jxTgZMwJ/upZDd+tQGrnTwDeESVdZG7gqAuXGcrup2Ai9nvibWlb+0MvB84DjhIzJUvbTYqvALci1Vs/De20tzKv98zMO1rdDJKtxYwtCtiAovd3RORQ4HDMAf7McRb9X57rMYqKD4BzEL12YKV1twCF67TEd1ewMXs98RaaikphyjSG5sf7we8HeEAYAKWRKAfXQ/Ob8Kc8ZegvITlH34WmE2hsIogAFUK2JfxvIvW6QQ9SsDFHPDkuq3fLAA17IQyAhP1eCyudVcsYmaIwECsB6+lzX4tQJNabOoaLFH9MiwudSGwCKgjxxpato6gee7dnqvA2TF6rIBLOeBv69p9XzF19oZeaqKtxeJXe9Nmv81Yip0Wgc010NTUmiV3a557lwvWiQcX8DY4cBuC3hHWPjOLeV/+aNq35DiO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4ziO4zjdjv8H0yEAwwBIeroAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjQtMDgtMTlUMTY6NDc6MTMrMDA6MDCR7GG5AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI0LTA0LTIzVDE1OjQ0OjQyKzAwOjAwHzv61wAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyNC0wOC0xOVQxNjo0ODo0NyswMDowMPoAiSAAAAAASUVORK5CYII=';