halkyon_oidc_server and its generated client halkyon_oidc_client: any provider following OpenID Connect's code flow, beside Serverpod's own providers, with a preset for SimpleLogin (no PKCE, no `typ`). Nothing of Halkyon's is in it; BSD-3-Clause, `publish_to: none` for now. - Attempts in `halkyon_oidc_attempt` (ADR 0020's D13): the ID PostgreSQL's gen_random_uuid(), a state and a nonce of 32 random bytes, PKCE's verifier where used, the caller if signed in; ten minutes, taken once. A host may begin the state with a marker. - The code exchanged by the module itself, so a provider out of reach (a timeout, a 5xx) is told apart from a refused code; the ID token checked with dart_jsonwebtoken's algorithms (ADR 0020's D12): the provider's algorithms alone, the key its `kid` names, of the right type; issuer, audience and `azp`, expiry, time issued, `nbf`, a minute's leeway; the attempt's nonce; a `sub`. - Keys kept an hour, fetched again for an unknown `kid` at most once a minute; discovery checked against the issuer; every request with a timeout. - Identities in `halkyon_oidc_identity`: the provider's key and the `sub`, one per person and provider, deleted with the person. - The stock endpoint (`OidcIdpBaseEndpoint`), and the parts for a host with rules of its own: begin, verify, keep and takeKept, link, accountOf, unlink; merging people; a limit per network. - The forwarding page, moved here from halkyon_base in the next commit. The package's main library is the code generator's, as halkyon_base's; a host imports `package:halkyon_oidc_server/oidc.dart`. ADR 0023's point 5 no longer names OAuth2PkceUtil, which the module doesn't use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
231 lines
7.5 KiB
Dart
231 lines
7.5 KiB
Dart
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
|
/* To generate run: "serverpod generate" */
|
|
|
|
// ignore_for_file: implementation_imports
|
|
// ignore_for_file: library_private_types_in_public_api
|
|
// ignore_for_file: non_constant_identifier_names
|
|
// ignore_for_file: public_member_api_docs
|
|
// ignore_for_file: type_literal_in_constant_pattern
|
|
// ignore_for_file: use_super_parameters
|
|
// ignore_for_file: invalid_use_of_internal_member
|
|
// ignore_for_file: dead_code, unnecessary_type_check
|
|
|
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
|
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
|
as _iacc;
|
|
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
|
|
as _iaic;
|
|
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
|
import 'oidc_refusal.dart' as _i8gvcysd;
|
|
import 'oidc_refused_exception.dart' as _i70wwg4b;
|
|
import 'oidc_start.dart' as _i4fk0f61;
|
|
export 'oidc_refusal.dart';
|
|
export 'oidc_refused_exception.dart';
|
|
export 'oidc_start.dart';
|
|
export 'client.dart';
|
|
|
|
class Protocol extends _isc.SerializationManager {
|
|
Protocol._();
|
|
|
|
factory Protocol() => _instance;
|
|
|
|
static final Protocol _instance = Protocol._();
|
|
|
|
final Set<_isc.SerializationManager> _hostProtocols = {};
|
|
|
|
void registerHostProtocol(
|
|
String projectName,
|
|
_isc.SerializationManager protocol,
|
|
) {
|
|
_hostProtocols.add(protocol);
|
|
}
|
|
|
|
static String? getClassNameFromObjectJson(dynamic data) {
|
|
if (data is! Map) return null;
|
|
final className = data['__className__'] as String?;
|
|
if (className == null) return null;
|
|
if (!className.startsWith('halkyon_oidc.')) return className;
|
|
return className.substring(13);
|
|
}
|
|
|
|
@override
|
|
T deserialize<T>(
|
|
dynamic data, [
|
|
Type? t,
|
|
]) {
|
|
t ??= T;
|
|
|
|
final dataClassName = getClassNameFromObjectJson(data);
|
|
if (dataClassName != null && dataClassName != getClassNameForType(t)) {
|
|
try {
|
|
return deserializeByClassName({
|
|
'className': dataClassName,
|
|
'data': data,
|
|
});
|
|
} on _isc.DeserializationClassNameNotFoundException catch (_) {
|
|
// If the className is not recognized (e.g., older client receiving
|
|
// data with a new subtype), fall back to deserializing without the
|
|
// className, using the expected type T.
|
|
}
|
|
}
|
|
|
|
if (t == _i8gvcysd.OidcRefusal) {
|
|
return _i8gvcysd.OidcRefusal.fromJson(data) as T;
|
|
}
|
|
if (t == _i70wwg4b.OidcRefusedException) {
|
|
return _i70wwg4b.OidcRefusedException.fromJson(data) as T;
|
|
}
|
|
if (t == _i4fk0f61.OidcStart) {
|
|
return _i4fk0f61.OidcStart.fromJson(data) as T;
|
|
}
|
|
if (t == _isc.getType<_i8gvcysd.OidcRefusal?>()) {
|
|
return (data != null ? _i8gvcysd.OidcRefusal.fromJson(data) : null) as T;
|
|
}
|
|
if (t == _isc.getType<_i70wwg4b.OidcRefusedException?>()) {
|
|
return (data != null
|
|
? _i70wwg4b.OidcRefusedException.fromJson(data)
|
|
: null)
|
|
as T;
|
|
}
|
|
if (t == _isc.getType<_i4fk0f61.OidcStart?>()) {
|
|
return (data != null ? _i4fk0f61.OidcStart.fromJson(data) : null) as T;
|
|
}
|
|
try {
|
|
return _iaic.Protocol().deserialize<T>(data, t);
|
|
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
|
try {
|
|
return _iacc.Protocol().deserialize<T>(data, t);
|
|
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
|
return super.deserialize<T>(data, t);
|
|
}
|
|
|
|
static String? getClassNameForType(Type type) {
|
|
return switch (type) {
|
|
_i8gvcysd.OidcRefusal => 'OidcRefusal',
|
|
_i70wwg4b.OidcRefusedException => 'OidcRefusedException',
|
|
_i4fk0f61.OidcStart => 'OidcStart',
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
@override
|
|
String? getClassNameForObject(Object? data) {
|
|
String? className = super.getClassNameForObject(data);
|
|
if (className != null) return className;
|
|
|
|
if (data is Map<String, dynamic> && data['__className__'] is String) {
|
|
return (data['__className__'] as String).replaceFirst(
|
|
'halkyon_oidc.',
|
|
'',
|
|
);
|
|
}
|
|
|
|
switch (data) {
|
|
case _i8gvcysd.OidcRefusal():
|
|
return 'OidcRefusal';
|
|
case _i70wwg4b.OidcRefusedException():
|
|
return 'OidcRefusedException';
|
|
case _i4fk0f61.OidcStart():
|
|
return 'OidcStart';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
dynamic deserializeByClassName(Map<String, dynamic> data) {
|
|
var dataClassName = data['className'];
|
|
if (dataClassName is! String) {
|
|
return super.deserializeByClassName(data);
|
|
}
|
|
if (dataClassName == 'OidcRefusal') {
|
|
return deserialize<_i8gvcysd.OidcRefusal>(data['data']);
|
|
}
|
|
if (dataClassName == 'OidcRefusedException') {
|
|
return deserialize<_i70wwg4b.OidcRefusedException>(data['data']);
|
|
}
|
|
if (dataClassName == 'OidcStart') {
|
|
return deserialize<_i4fk0f61.OidcStart>(data['data']);
|
|
}
|
|
return super.deserializeByClassName(data);
|
|
}
|
|
|
|
@override
|
|
Object? dynamicFieldToJson(
|
|
Object? object, {
|
|
bool forProtocol = false,
|
|
}) {
|
|
if ((object is List || object is Set || object is Map) ||
|
|
getClassNameForObject(object) != null) {
|
|
return super.dynamicFieldToJson(object, forProtocol: forProtocol);
|
|
}
|
|
for (final protocol in _hostProtocols) {
|
|
final className = protocol.getClassNameForObject(object);
|
|
if (className == null) continue;
|
|
final host = protocol.getModuleName();
|
|
final wrapped = {
|
|
'className': className.contains('.') ? className : '$host.$className',
|
|
'data': object,
|
|
};
|
|
return forProtocol
|
|
? _isc.SerializationManager.toEncodableForProtocol(wrapped)
|
|
: _isc.SerializationManager.toEncodable(wrapped);
|
|
}
|
|
return super.dynamicFieldToJson(object, forProtocol: forProtocol);
|
|
}
|
|
|
|
@override
|
|
dynamic deserializeDynamicFieldValue(Object? value) {
|
|
if (value == null) return null;
|
|
if (value is! Map<String, dynamic> || value['className'] is! String) {
|
|
throw FormatException(
|
|
'Dynamic fields are encoded as a Map with className and data, but got '
|
|
'${value.runtimeType} instead.',
|
|
);
|
|
}
|
|
final className = value['className'] as String;
|
|
for (final protocol in _hostProtocols) {
|
|
final host = protocol.getModuleName();
|
|
final hostPrefix = '$host.';
|
|
if (className.startsWith(hostPrefix)) {
|
|
final strippedClassName = className.substring(hostPrefix.length);
|
|
if (strippedClassName.contains('.')) {
|
|
throw FormatException(
|
|
'Dynamic field className must not use multiple prefixes: $className',
|
|
);
|
|
}
|
|
final hostData = Map<String, dynamic>.from(value);
|
|
hostData['className'] = strippedClassName;
|
|
return protocol.deserializeByClassName(hostData);
|
|
}
|
|
}
|
|
if (className.contains('.')) {
|
|
for (final protocol in _hostProtocols) {
|
|
try {
|
|
return protocol.deserializeByClassName(value);
|
|
} on _isc.DeserializationClassNameNotFoundException catch (_) {}
|
|
}
|
|
}
|
|
return deserializeByClassName(value);
|
|
}
|
|
|
|
@override
|
|
String getModuleName() => 'halkyon_oidc';
|
|
|
|
/// Maps any `Record`s known to this [Protocol] to their JSON representation
|
|
///
|
|
/// Throws in case the record type is not known.
|
|
///
|
|
/// This method will return `null` (only) for `null` inputs.
|
|
Map<String, dynamic>? mapRecordToJson(Record? record) {
|
|
if (record == null) {
|
|
return null;
|
|
}
|
|
try {
|
|
return _iaic.Protocol().mapRecordToJson(record);
|
|
} catch (_) {}
|
|
try {
|
|
return _iacc.Protocol().mapRecordToJson(record);
|
|
} catch (_) {}
|
|
throw Exception('Unsupported record type ${record.runtimeType}');
|
|
}
|
|
}
|