Skip to content

Commit

Permalink
feat: Cloud Auth (#214)
Browse files Browse the repository at this point in the history
Adds a new microservice for embedding into Celest projects which provides a fully Dart-native authentication and authorization service on top of existing Celest projects.

Currently it supports email OTP but more providers are coming soon!
  • Loading branch information
dnys1 authored Oct 14, 2024
1 parent 1adbe8b commit 9e37c26
Show file tree
Hide file tree
Showing 109 changed files with 22,011 additions and 1,150 deletions.
9 changes: 7 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
1 change: 1 addition & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: celest_dev
packages:
- examples/**
- packages/**
- services/**
ignore:
- "examples/**/celest"
- "packages/**/example/celest"
Expand Down
73 changes: 4 additions & 69 deletions packages/celest/lib/src/auth/auth_provider.dart
Original file line number Diff line number Diff line change
@@ -1,48 +1,21 @@
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}
const 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}
Expand Down Expand Up @@ -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
Expand Down
72 changes: 67 additions & 5 deletions packages/celest/lib/src/core/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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<ContextKey<Object>, Object> _values = {};

Expand Down Expand Up @@ -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<V extends Object>(ContextKey<V> key) {
if (key.read(this) case final value?) {
return (this, value);
Expand All @@ -187,8 +198,9 @@ final class Context {
}

/// Sets the value of [key] in the current [Context].
void put<V extends Object>(ContextKey<V> key, V value) {
V put<V extends Object>(ContextKey<V> key, V value) {
key.set(this, value);
return value;
}

/// Sets the value of [key] in this [Context] if it is not already set.
Expand Down Expand Up @@ -219,6 +231,21 @@ final class Context {
V? remove<V extends Object>(ContextKey<V> key) {
return _values.remove(key) as V?;
}

final List<PostRequestCallback> _postRequestCallbacks = [];

/// Registers a callback to be run after the current context completes.
void after(
FutureOr<void> Function(shelf.Response) callback, {
FutureOr<void> 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}
Expand Down Expand Up @@ -255,6 +282,12 @@ abstract interface class ContextKey<V extends Object> {
/// The context key for the context [ResolvedProject].
static const ContextKey<ResolvedProject> 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> router = ContextKey('router');

/// Reads the value for `this` from the given [context].
V? read(Context context);

Expand Down Expand Up @@ -301,3 +334,32 @@ final class _ContextKey<V extends Object> implements ContextKey<V> {
final class _PrincipalContextKey extends _ContextKey<User> {
const _PrincipalContextKey() : super('principal');
}

/// A callback to be run after the current context completes.
typedef PostRequestCallback = ({
FutureOr<void> Function(shelf.Response) onResponse,
FutureOr<void> 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<List<PostRequestCallback>> {
/// {@macro celest.runtime.post_request_callbacks}
const PostRequestCallbacks();

@override
List<PostRequestCallback> read(Context context) {
return context._postRequestCallbacks;
}

@override
void set(Context context, List<PostRequestCallback>? value) {
if (value == null) {
context._postRequestCallbacks.clear();
} else {
context._postRequestCallbacks.addAll(value);
}
}
}
Loading

0 comments on commit 9e37c26

Please sign in to comment.