feat(oidc): a Serverpod module for signing in with OpenID Connect, SimpleLogin a preset (ADR 0023)
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>
This commit is contained in:
parent
630226b51e
commit
f549fa59df
44 changed files with 15870 additions and 2 deletions
19
packages/halkyon_oidc_server/.gitignore
vendored
Normal file
19
packages/halkyon_oidc_server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
||||
# Omit committing pubspec.lock for library packages:
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock
|
||||
pubspec.lock
|
||||
|
||||
# Conventional directory for build outputs
|
||||
build/
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Passwords file: the tests' throwaway values, never committed.
|
||||
config/passwords.yaml
|
||||
|
||||
# Embedded PostgreSQL runtime artifacts (data dir, sockets, pidfiles)
|
||||
.serverpod/
|
||||
5
packages/halkyon_oidc_server/CHANGELOG.md
Normal file
5
packages/halkyon_oidc_server/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
## 0.1.0
|
||||
|
||||
* Signing in with OpenID Connect's code flow, with a SimpleLogin preset: attempts,
|
||||
the ID token's checks, identities, the stock endpoint and the parts, the forwarding
|
||||
page.
|
||||
28
packages/halkyon_oidc_server/LICENSE
Normal file
28
packages/halkyon_oidc_server/LICENSE
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2026, the Project Halkyon authors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
163
packages/halkyon_oidc_server/README.md
Normal file
163
packages/halkyon_oidc_server/README.md
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# halkyon_oidc
|
||||
|
||||
Signing in with OpenID Connect for Serverpod v4, as a Serverpod module
|
||||
(`halkyon_oidc_server`, with its generated client `halkyon_oidc_client`): any provider
|
||||
following OpenID Connect's code flow (Keycloak, Authentik, Zitadel, Authelia, Pocket ID,
|
||||
…) beside Serverpod's own providers, with a preset for SimpleLogin, the one tried live.
|
||||
Halkyon signs in with it (ADR 0020, ADR 0023); nothing of Halkyon's is in it.
|
||||
|
||||
## What it does
|
||||
|
||||
- **Begins a sign-in** (`OidcIdp.begin`): an attempt in `halkyon_oidc_attempt`, its ID
|
||||
PostgreSQL's `gen_random_uuid()` (122 random bits), the secret only the device holds,
|
||||
with a state and a nonce of 32 random bytes each, PKCE's verifier where the provider
|
||||
takes PKCE, and who began it, if signed in. It answers with the attempt, the address to
|
||||
send the person to, and the state. Attempts last ten minutes and are ended once.
|
||||
- **Ends it** (`OidcIdp.verify`): the attempt taken out first, so it ends once whatever
|
||||
follows; the code exchanged with the client's secret (HTTP Basic, or in the form); the
|
||||
ID token checked: three parts, the header's algorithm one of the provider's
|
||||
(asymmetric only, no `typ` asked for, as SimpleLogin sends none), the key the `kid`
|
||||
names among the provider's keys, of the algorithm's type; the signature; the issuer,
|
||||
the audience (and `azp` with more than one, or wherever it is given), the expiry, the
|
||||
time issued and `nbf`, each with a minute of clocks' difference; the attempt's nonce;
|
||||
a `sub`. What it proves is the provider's key and the `sub`, with the token's other
|
||||
claims for the host's code, which nothing keeps.
|
||||
- **Keeps a person's identity** at a provider (`halkyon_oidc_identity`): the provider's
|
||||
key and the `sub`, nothing else. Each `sub` is one person's at a provider, and a
|
||||
person has one per provider. It goes with the person (a foreign key to Serverpod's
|
||||
`AuthUser`, deleted with it), as do their unfinished attempts.
|
||||
- **Asks providers sparingly**: the discovery document once (checked to name the
|
||||
provider's issuer and its three endpoints, asked again after a failure), the keys
|
||||
for an hour, fetched again for an unknown `kid` at most once a minute; every request
|
||||
with a timeout (10 s unless said), a provider out of reach refused as
|
||||
`providerUnavailable`, never as a refused sign-in.
|
||||
- **Limits the sign-ins one network begins** (30 an hour unless said, Serverpod's
|
||||
`DatabaseRateLimiter`, domain `halkyon_oidc`), counting by the address the host gives.
|
||||
|
||||
Refusals are `OidcRefusedException`s with an `OidcRefusal`: `unknownProvider`,
|
||||
`unknownAttempt` (nobody began it, it ended, or its time ran out), `wrongCaller`,
|
||||
`rejected` (the provider refused the code, or the token proves nothing; the server
|
||||
logs why), `providerUnavailable`, `takenByAnotherPerson`, `otherIdentityLinked`,
|
||||
`tooManyAttempts`.
|
||||
|
||||
## Setting it up
|
||||
|
||||
1. **Depend on it** (`halkyon_oidc_server` in the server, `halkyon_oidc_client` in the
|
||||
client), and name it in the server's `config/generator.yaml`:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
halkyon_oidc:
|
||||
nickname: oidc
|
||||
```
|
||||
|
||||
2. **Name the providers** where the server sets up its sign-in
|
||||
(`package:halkyon_oidc_server/oidc.dart`), each under a key of its own that never
|
||||
changes, as it is kept with every identity:
|
||||
|
||||
```dart
|
||||
pod.initializeAuthServices(
|
||||
tokenManagerBuilders: [ServerSideSessionsConfigFromPasswords()],
|
||||
identityProviderBuilders: [
|
||||
OidcIdpConfig(
|
||||
providers: [
|
||||
// Issuer, endpoints, no PKCE, RS256: what SimpleLogin does.
|
||||
OidcProvider.simpleLogin(
|
||||
clientId: pod.getPassword('simpleLoginClientId')!,
|
||||
clientSecret: pod.getPassword('simpleLoginClientSecret')!,
|
||||
callbackUrl: Uri.parse('https://example.org/simplelogin-callback'),
|
||||
),
|
||||
// Endpoints from the issuer's discovery document.
|
||||
OidcProvider(
|
||||
key: 'keycloak',
|
||||
issuer: 'https://id.example.org/realms/main',
|
||||
clientId: 'my-app',
|
||||
clientSecret: pod.getPassword('keycloakClientSecret')!,
|
||||
callbackUrl: Uri.parse('my.app://oidc-callback'),
|
||||
scopes: const ['openid', 'email'],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
`OidcProvider` takes the scopes (`openid` alone unless said), PKCE (on unless said),
|
||||
the algorithms its tokens may be signed with (`RS256` unless said), the endpoints
|
||||
where it has no discovery document, and how the client gives its secret.
|
||||
`OidcIdpConfig` takes how long attempts last, the limit per network, the timeout and
|
||||
the HTTP client.
|
||||
|
||||
3. **Add the endpoint**, either way below, then `serverpod generate` and
|
||||
`serverpod create-migration`, which adds the module's two tables and its version
|
||||
row to the server's migrations.
|
||||
|
||||
### The stock way
|
||||
|
||||
As Serverpod's own providers: one endpoint in the host's server,
|
||||
|
||||
```dart
|
||||
class OidcIdpEndpoint extends OidcIdpBaseEndpoint {}
|
||||
```
|
||||
|
||||
with `begin(provider)`, `login(attempt, code)` (a session for the person with that
|
||||
identity, a new person for one nobody has), `link(attempt, code)` (the identity added to
|
||||
the signed-in caller, who began the attempt signed in) and `hasAccount`. Sessions it
|
||||
issues name the method `oidc`.
|
||||
|
||||
`begin` counts its limit by the request's `remoteInfo`: the connection's address, or the
|
||||
first address of a `Forwarded` or `X-Forwarded-For` header, whoever wrote it. Behind a
|
||||
proxy that passes either on as the visitor sent it, override `begin` and pass the
|
||||
address the proxy saw as `network`.
|
||||
|
||||
### The parts
|
||||
|
||||
For a host with rules of its own (who may have an account, a name chosen first), its own
|
||||
endpoints call `AuthServices.instance.oidcIdp` (`package:halkyon_oidc_server/oidc.dart`,
|
||||
the host's library; the package's main library is the code generator's):
|
||||
|
||||
- `begin(session, provider:, caller:, statePrefix:, network:)`: an attempt.
|
||||
- `verify(session, attempt:, code:, caller:)`: the identity proven
|
||||
(`VerifiedOidcIdentity`: the provider's key, the `sub`, the claims, the caller),
|
||||
touching no account.
|
||||
- `keep(session, attempt:, identity:, keepFor:)`: the identity kept that long under the
|
||||
attempt's ID, which `takeKept` takes once, as when a person chooses a name before their
|
||||
account is made.
|
||||
- `accountOf`, `hasIdentity`, `link`, `unlink`: the identities.
|
||||
- `forgetExpiredAttempts`: `begin` forgets them on the way; call it daily as well.
|
||||
|
||||
`mergeAuthUsers` moves one person's identities to another, refusing where both have one
|
||||
at the same provider.
|
||||
|
||||
## The forwarding page
|
||||
|
||||
Some providers take only https callbacks, each registered exactly (SimpleLogin), so a
|
||||
desktop app's loopback address or a phone's scheme can't be one. The host then registers
|
||||
one page of its web server, and the page sends each sign-in on to the app that began it:
|
||||
|
||||
```dart
|
||||
addOidcCallbackForwarding(
|
||||
pod,
|
||||
path: '/simplelogin-callback',
|
||||
returns: {
|
||||
'desktop': Uri.parse('http://127.0.0.1:43823/oidc-callback'),
|
||||
'phone': Uri.parse('my.app://oidc-callback'),
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
The app begins with `statePrefix: 'desktop.'` (or `'phone.'`); the page answers a 303
|
||||
to the address the marker names, with the provider's `code` (or its `error`) and the
|
||||
`state`, each exactly once and well formed, and refuses anything else with a page that
|
||||
repeats nothing it was given (`refusalPage` for the host's own, in its look and
|
||||
languages). It keeps and logs nothing: a code caught on its way is worthless without the
|
||||
attempt the app holds. `oidcCallbackForwarding` is the page itself, for tests.
|
||||
|
||||
## Tests
|
||||
|
||||
`dart test` here starts its own PostgreSQL (`config/test.yaml`), with throwaway
|
||||
`database`, `serviceSecret` and `serverSideSessionKeyHashPepper` in a git-ignored
|
||||
`config/passwords.yaml`. The providers in the tests are fakes that keep to the
|
||||
specification, keys generated per run; the ID token's checks are tested one by one.
|
||||
|
||||
Under the BSD 3-Clause licence, Serverpod's own.
|
||||
22
packages/halkyon_oidc_server/analysis_options.yaml
Normal file
22
packages/halkyon_oidc_server/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# The workspace's lint rules (see ../../analysis_options.yaml), minus generated code.
|
||||
include: ../../analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/src/generated/**
|
||||
- test/integration/test_tools/serverpod_test_tools.dart
|
||||
|
||||
# Serverpod's code generator reads this package with an analyzer that doesn't know
|
||||
# Dart v3.13's primary constructors yet, and fails on them in endpoint files ("requires
|
||||
# the 'primary-constructors' language feature"; checked with v4.0.4). So the module
|
||||
# keeps the classic constructor and class-body syntax, as the Halkyon servers do;
|
||||
# switch these back on once Serverpod parses it.
|
||||
linter:
|
||||
rules:
|
||||
empty_container_bodies: false
|
||||
unnecessary_type_name_in_constructor: false
|
||||
use_declaring_parameters: false
|
||||
use_primary_constructors: false
|
||||
|
||||
formatter:
|
||||
trailing_commas: preserve
|
||||
7
packages/halkyon_oidc_server/config/generator.yaml
Normal file
7
packages/halkyon_oidc_server/config/generator.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
type: module
|
||||
# The name its endpoints have in a host's client (`client.oidc.…`); a host may
|
||||
# choose another in its own generator.yaml.
|
||||
nickname: oidc
|
||||
|
||||
client_package_path: ../halkyon_oidc_client
|
||||
server_test_tools_path: test/integration/test_tools
|
||||
40
packages/halkyon_oidc_server/config/test.yaml
Normal file
40
packages/halkyon_oidc_server/config/test.yaml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# The module's test server: every port 0, so the tests find free ones and can run
|
||||
# side by side. The database is the embedded PostgreSQL Serverpod starts and
|
||||
# manages itself (`dataPath`): no Docker needed, `dart test` is enough. Its password
|
||||
# and the tests' other secrets are in config/passwords.yaml (git-ignored; see the
|
||||
# README for the keys).
|
||||
|
||||
apiServer:
|
||||
port: 0
|
||||
publicHost: localhost
|
||||
publicPort: 0
|
||||
publicScheme: http
|
||||
|
||||
insightsServer:
|
||||
port: 0
|
||||
publicHost: localhost
|
||||
publicPort: 0
|
||||
publicScheme: http
|
||||
|
||||
webServer:
|
||||
port: 0
|
||||
publicHost: localhost
|
||||
publicPort: 0
|
||||
publicScheme: http
|
||||
|
||||
database:
|
||||
host: localhost
|
||||
port: 9090
|
||||
name: halkyon_oidc_test
|
||||
user: postgres
|
||||
dataPath: .serverpod/test/pgdata
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
host: localhost
|
||||
port: 9091
|
||||
|
||||
# Session logs are never kept, for privacy, as on every Halkyon server.
|
||||
sessionLogs:
|
||||
persistentEnabled: false
|
||||
consoleEnabled: true
|
||||
2
packages/halkyon_oidc_server/dart_test.yaml
Normal file
2
packages/halkyon_oidc_server/dart_test.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
tags:
|
||||
integration: {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/// What Serverpod's code generator needs to put the module into a host's
|
||||
/// server; not for the host's own code, which imports `oidc.dart`.
|
||||
library;
|
||||
|
||||
export 'src/generated/endpoints.dart';
|
||||
export 'src/generated/protocol.dart';
|
||||
13
packages/halkyon_oidc_server/lib/oidc.dart
Normal file
13
packages/halkyon_oidc_server/lib/oidc.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/// Signing in with OpenID Connect for Serverpod (ADR 0023): any provider
|
||||
/// that follows the code flow, SimpleLogin a preset; Serverpod's contract for
|
||||
/// providers of one's own, its stock endpoint, and the parts for a host with
|
||||
/// rules of its own.
|
||||
library;
|
||||
|
||||
export 'src/callback_forwarding.dart';
|
||||
export 'src/endpoints/oidc_idp_base_endpoint.dart';
|
||||
export 'src/generated/protocol.dart' hide Protocol;
|
||||
export 'src/id_token.dart' show IdTokenRejected, checkIdToken, idTokenLeeway;
|
||||
export 'src/oidc_idp.dart';
|
||||
export 'src/oidc_provider.dart';
|
||||
export 'src/provider_keys.dart' show ProviderKeys;
|
||||
198
packages/halkyon_oidc_server/lib/src/callback_forwarding.dart
Normal file
198
packages/halkyon_oidc_server/lib/src/callback_forwarding.dart
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
// A forwarding page for a provider's callbacks (ADR 0023, 7): for a provider
|
||||
// that takes https callbacks alone, each registered exactly (SimpleLogin), so
|
||||
// a desktop app's loopback or a phone's scheme can't be one. The host
|
||||
// registers this page's address; the browser comes back here with the
|
||||
// provider's code (or its error) and the state the sign-in began with, and
|
||||
// the page sends both on to the app that began it. The state starts with a
|
||||
// marker the host chose (`OidcIdp.begin`'s `statePrefix`), which names an
|
||||
// address of the host's own table, so the page sends nobody anywhere else.
|
||||
// It keeps and logs nothing (the web server's session names the path alone):
|
||||
// a code caught on its way is worthless without the attempt the app holds.
|
||||
|
||||
/// A state as the page takes it: a marker (letters, digits, `-` and `_`, up
|
||||
/// to 64), a dot, and the rest the sign-in began with, 1 to 512 characters
|
||||
/// of base64url (`learn-desktop.q3J…`). Nothing else, so nothing in it can
|
||||
/// reach the address it goes to but as the value of `state`.
|
||||
final _state = RegExp(r'^([A-Za-z0-9_-]{1,64})\.([A-Za-z0-9_-]{1,512})$');
|
||||
|
||||
/// A code of RFC 3986's unreserved characters, up to 512 (SimpleLogin's are
|
||||
/// ten lower-case letters).
|
||||
final _code = RegExp(r'^[A-Za-z0-9._~-]{1,512}$');
|
||||
|
||||
/// An error as OAuth names them (RFC 6749, 4.1.2.1; SimpleLogin's own is
|
||||
/// `deny`).
|
||||
final _error = RegExp(r'^[A-Za-z0-9._-]{1,64}$');
|
||||
|
||||
/// The longest query the page reads: a sign-in's is far shorter.
|
||||
const _maxQuery = 4096;
|
||||
|
||||
/// A request for the page as it reads one: plain values, so tests can make
|
||||
/// one. The route turns Relic's requests into these.
|
||||
typedef OidcCallbackRequest = ({
|
||||
/// The method, upper case.
|
||||
String method,
|
||||
|
||||
/// The query as the browser sent it, still encoded.
|
||||
String query,
|
||||
|
||||
/// The `Accept-Language` header, if any.
|
||||
String? acceptLanguage,
|
||||
});
|
||||
|
||||
/// What the page answers: its status, where it sends the browser (`location`,
|
||||
/// with an empty `html`) or the page saying why not, and its headers, by
|
||||
/// lower-case name.
|
||||
typedef OidcCallbackAnswer = ({
|
||||
int status,
|
||||
Uri? location,
|
||||
String html,
|
||||
Map<String, String> headers,
|
||||
});
|
||||
|
||||
/// The page a refused callback shows, for a visitor whose browser asks for
|
||||
/// [acceptLanguage]; it can say nothing of what came, as it is given none
|
||||
/// of it.
|
||||
typedef OidcRefusalPage = String Function(String? acceptLanguage);
|
||||
|
||||
/// The page's answer to [request]: a 303 to the address [returns] has for
|
||||
/// the state's marker, with the code or the error, and the state, where
|
||||
/// exactly one of them came, each once and well formed; [refusalPage]
|
||||
/// otherwise, a 400 (a 405 for any method but GET).
|
||||
OidcCallbackAnswer oidcCallbackForwarding(
|
||||
OidcCallbackRequest request, {
|
||||
required Map<String, Uri> returns,
|
||||
OidcRefusalPage refusalPage = defaultOidcRefusalPage,
|
||||
}) {
|
||||
OidcCallbackAnswer refused(int status, {String? allow}) => (
|
||||
status: status,
|
||||
location: null,
|
||||
html: refusalPage(request.acceptLanguage),
|
||||
headers: {
|
||||
..._kept,
|
||||
// Nothing runs, nothing is fetched, and no other page frames it.
|
||||
'content-security-policy':
|
||||
"default-src 'none'; style-src 'unsafe-inline'; "
|
||||
"frame-ancestors 'none'",
|
||||
'allow': ?allow,
|
||||
},
|
||||
);
|
||||
|
||||
if (request.method != 'GET') {
|
||||
return refused(405, allow: 'GET');
|
||||
}
|
||||
if (request.query.length > _maxQuery) {
|
||||
return refused(400);
|
||||
}
|
||||
final Map<String, List<String>> query;
|
||||
try {
|
||||
query = Uri(query: request.query).queryParametersAll;
|
||||
} on Object {
|
||||
// Encoding Uri can't read: FormatException or ArgumentError.
|
||||
return refused(400);
|
||||
}
|
||||
String? only(String name) => switch (query[name]) {
|
||||
[final value] => value,
|
||||
_ => null,
|
||||
};
|
||||
bool twice(String name) => (query[name]?.length ?? 0) > 1;
|
||||
if (twice('state') || twice('code') || twice('error')) {
|
||||
return refused(400);
|
||||
}
|
||||
final state = only('state');
|
||||
final target = switch (_state.firstMatch(state ?? '')) {
|
||||
final match? => returns[match[1]],
|
||||
null => null,
|
||||
};
|
||||
final code = only('code');
|
||||
final error = only('error');
|
||||
if (target == null ||
|
||||
state == null ||
|
||||
(code == null) == (error == null) ||
|
||||
(code != null && !_code.hasMatch(code)) ||
|
||||
(error != null && !_error.hasMatch(error))) {
|
||||
return refused(400);
|
||||
}
|
||||
return (
|
||||
status: 303,
|
||||
location: target.replace(
|
||||
queryParameters: {'code': ?code, 'error': ?error, 'state': state},
|
||||
),
|
||||
html: '',
|
||||
headers: _kept,
|
||||
);
|
||||
}
|
||||
|
||||
/// What every answer says to whoever passes it on: keep nothing, send no
|
||||
/// referrer with the code in it, guess no type.
|
||||
const _kept = {
|
||||
'cache-control': 'no-store',
|
||||
'referrer-policy': 'no-referrer',
|
||||
'x-content-type-options': 'nosniff',
|
||||
};
|
||||
|
||||
/// The module's own refusal page, in English, unstyled; a host gives its own
|
||||
/// for its look and languages.
|
||||
String defaultOidcRefusalPage(String? acceptLanguage) =>
|
||||
'<!doctype html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n'
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1">\n'
|
||||
'<meta name="robots" content="noindex">\n'
|
||||
'<title>Sign-in not completed</title>\n</head>\n<body>\n'
|
||||
"<h1>This sign-in can't go on.</h1>\n"
|
||||
'<p>Go back to the app and sign in again.</p>\n</body>\n</html>\n';
|
||||
|
||||
/// Serves the forwarding page on [pod]'s web server at [path], sending each
|
||||
/// sign-in to the address [returns] has for its state's marker (see
|
||||
/// [oidcCallbackForwarding]); nothing where the pod has no web server.
|
||||
void addOidcCallbackForwarding(
|
||||
Serverpod pod, {
|
||||
required String path,
|
||||
required Map<String, Uri> returns,
|
||||
OidcRefusalPage refusalPage = defaultOidcRefusalPage,
|
||||
}) {
|
||||
if (pod.config.webServer == null) {
|
||||
return;
|
||||
}
|
||||
pod.webServer.addRoute(
|
||||
_CallbackForwardingRoute(returns, refusalPage),
|
||||
path,
|
||||
);
|
||||
}
|
||||
|
||||
/// Every method, so the page answers them itself rather than another route.
|
||||
class _CallbackForwardingRoute extends Route {
|
||||
_CallbackForwardingRoute(this._returns, this._refusalPage)
|
||||
: super(methods: Method.values.toSet());
|
||||
|
||||
final Map<String, Uri> _returns;
|
||||
final OidcRefusalPage _refusalPage;
|
||||
|
||||
@override
|
||||
Future<Result> handleCall(Session session, Request request) async {
|
||||
final answer = oidcCallbackForwarding(
|
||||
(
|
||||
method: request.method.value,
|
||||
query: request.url.query,
|
||||
acceptLanguage: request.headers[HttpHeaders.acceptLanguageHeader]?.join(
|
||||
',',
|
||||
),
|
||||
),
|
||||
returns: _returns,
|
||||
refusalPage: _refusalPage,
|
||||
);
|
||||
return Response(
|
||||
answer.status,
|
||||
body: answer.location == null
|
||||
? Body.fromString(answer.html, mimeType: MimeType.html)
|
||||
: Body.empty(),
|
||||
headers: Headers.fromMap({
|
||||
for (final MapEntry(:key, :value) in answer.headers.entries)
|
||||
key: [value],
|
||||
if (answer.location case final location?) 'location': ['$location'],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:halkyon_oidc_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_oidc_server/src/oidc_idp.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// The stock endpoint of OpenID Connect sign-in (ADR 0023, 6), as
|
||||
/// Serverpod's own providers have theirs: a host extends it in its own
|
||||
/// server (`class OidcIdpEndpoint extends OidcIdpBaseEndpoint {}`), which
|
||||
/// puts it into the host's client.
|
||||
///
|
||||
/// Its per-network limit counts by the request's `remoteInfo`, the connection's
|
||||
/// address, or the `Forwarded` or `X-Forwarded-For` header's first address
|
||||
/// where one came, whoever wrote it: behind a proxy that passes either on as
|
||||
/// the visitor sent it, a host overrides [begin] and counts by the address
|
||||
/// its proxy saw.
|
||||
abstract class OidcIdpBaseEndpoint extends IdpBaseEndpoint {
|
||||
/// The OpenID Connect sign-in the host set up.
|
||||
OidcIdp get oidcIdp => AuthServices.instance.oidcIdp;
|
||||
|
||||
/// Begins a sign-in with [provider] (its key): signed in, to add the
|
||||
/// identity to the caller ([link]); signed out, to sign in ([login]).
|
||||
Future<OidcStart> begin(Session session, {required String provider}) =>
|
||||
oidcIdp.begin(
|
||||
session,
|
||||
provider: provider,
|
||||
caller: session.authenticated?.authUserId,
|
||||
network: session.request?.remoteInfo,
|
||||
);
|
||||
|
||||
/// Signs in with the identity [code] proves for [attempt], begun signed
|
||||
/// out: a session for its person, made for one nobody has.
|
||||
Future<AuthSuccess> login(
|
||||
Session session, {
|
||||
required UuidValue attempt,
|
||||
required String code,
|
||||
}) => oidcIdp.login(session, attempt: attempt, code: code);
|
||||
|
||||
/// Links the identity [code] proves for [attempt], begun by the caller
|
||||
/// signed in, to them.
|
||||
Future<void> link(
|
||||
Session session, {
|
||||
required UuidValue attempt,
|
||||
required String code,
|
||||
}) async {
|
||||
final caller =
|
||||
session.authenticated?.authUserId ??
|
||||
(throw OidcRefusedException(reason: OidcRefusal.wrongCaller));
|
||||
await oidcIdp.linkToCaller(
|
||||
session,
|
||||
attempt: attempt,
|
||||
code: code,
|
||||
caller: caller,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> hasAccount(Session session) async {
|
||||
final caller = session.authenticated?.authUserId;
|
||||
return caller != null &&
|
||||
await oidcIdp.hasIdentity(session, authUserId: caller);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/* 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: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
|
||||
class Endpoints extends _is.EndpointDispatch {
|
||||
@override
|
||||
void initializeEndpoints(_is.Server server) {
|
||||
modules['serverpod_auth_idp'] = _iais.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_auth_core'] = _iacs.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
}
|
||||
}
|
||||
852
packages/halkyon_oidc_server/lib/src/generated/oidc_attempt.dart
Normal file
852
packages/halkyon_oidc_server/lib/src/generated/oidc_attempt.dart
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
/* 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_null_comparison
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_oidc_server/src/generated/protocol.dart' as _i7me1mu7;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
|
||||
/// A sign-in a device began with a provider (`OidcIdp.begin`), until it is
|
||||
/// ended (`OidcIdp.verify`), once, within ten minutes (ADR 0023, 4); or the
|
||||
/// identity it proved, kept a while for the host to finish with
|
||||
/// (`OidcIdp.takeKept`).
|
||||
abstract class OidcAttempt
|
||||
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
|
||||
OidcAttempt._({
|
||||
this.id,
|
||||
required this.provider,
|
||||
required this.state,
|
||||
required this.nonce,
|
||||
this.codeVerifier,
|
||||
this.callerId,
|
||||
this.caller,
|
||||
this.subject,
|
||||
required this.expiresAt,
|
||||
});
|
||||
|
||||
factory OidcAttempt({
|
||||
_is.UuidValue? id,
|
||||
required String provider,
|
||||
required String state,
|
||||
required String nonce,
|
||||
String? codeVerifier,
|
||||
_is.UuidValue? callerId,
|
||||
_iacs.AuthUser? caller,
|
||||
String? subject,
|
||||
required DateTime expiresAt,
|
||||
}) = _OidcAttemptImpl;
|
||||
|
||||
factory OidcAttempt.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return OidcAttempt(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
provider: jsonSerialization['provider'] as String,
|
||||
state: jsonSerialization['state'] as String,
|
||||
nonce: jsonSerialization['nonce'] as String,
|
||||
codeVerifier: jsonSerialization['codeVerifier'] as String?,
|
||||
callerId: jsonSerialization['callerId'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['callerId']),
|
||||
caller: jsonSerialization['caller'] == null
|
||||
? null
|
||||
: _i7me1mu7.Protocol().deserialize<_iacs.AuthUser>(
|
||||
jsonSerialization['caller'],
|
||||
),
|
||||
subject: jsonSerialization['subject'] as String?,
|
||||
expiresAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['expiresAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = OidcAttemptTable();
|
||||
|
||||
static const db = OidcAttemptRepository._();
|
||||
|
||||
@override
|
||||
_is.UuidValue? id;
|
||||
|
||||
/// The provider, by its key (`OidcProvider.key`).
|
||||
String provider;
|
||||
|
||||
/// The state sent with the person, which comes back with the code.
|
||||
String state;
|
||||
|
||||
/// The nonce the provider's ID token must carry.
|
||||
String nonce;
|
||||
|
||||
/// PKCE's code verifier; null for a provider without PKCE.
|
||||
String? codeVerifier;
|
||||
|
||||
_is.UuidValue? callerId;
|
||||
|
||||
/// Who began it signed in, to have the identity linked to; null for a
|
||||
/// sign-in.
|
||||
_iacs.AuthUser? caller;
|
||||
|
||||
/// The ID the provider proved, for an attempt kept after it was verified;
|
||||
/// null before.
|
||||
String? subject;
|
||||
|
||||
/// Until when it can be ended, or taken once verified.
|
||||
DateTime expiresAt;
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [OidcAttempt]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
OidcAttempt copyWith({
|
||||
_is.UuidValue? id,
|
||||
String? provider,
|
||||
String? state,
|
||||
String? nonce,
|
||||
String? codeVerifier,
|
||||
_is.UuidValue? callerId,
|
||||
_iacs.AuthUser? caller,
|
||||
String? subject,
|
||||
DateTime? expiresAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcAttempt',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
'provider': provider,
|
||||
'state': state,
|
||||
'nonce': nonce,
|
||||
if (codeVerifier != null) 'codeVerifier': codeVerifier,
|
||||
if (callerId != null) 'callerId': callerId?.toJson(),
|
||||
if (caller != null) 'caller': caller?.toJson(),
|
||||
if (subject != null) 'subject': subject,
|
||||
'expiresAt': expiresAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static OidcAttemptInclude include({_iacs.AuthUserInclude? caller}) {
|
||||
return OidcAttemptInclude._(caller: caller);
|
||||
}
|
||||
|
||||
static OidcAttemptIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
OidcAttemptInclude? include,
|
||||
}) {
|
||||
return OidcAttemptIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _OidcAttemptImpl extends OidcAttempt {
|
||||
_OidcAttemptImpl({
|
||||
_is.UuidValue? id,
|
||||
required String provider,
|
||||
required String state,
|
||||
required String nonce,
|
||||
String? codeVerifier,
|
||||
_is.UuidValue? callerId,
|
||||
_iacs.AuthUser? caller,
|
||||
String? subject,
|
||||
required DateTime expiresAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
provider: provider,
|
||||
state: state,
|
||||
nonce: nonce,
|
||||
codeVerifier: codeVerifier,
|
||||
callerId: callerId,
|
||||
caller: caller,
|
||||
subject: subject,
|
||||
expiresAt: expiresAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [OidcAttempt]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
OidcAttempt copyWith({
|
||||
Object? id = _Undefined,
|
||||
String? provider,
|
||||
String? state,
|
||||
String? nonce,
|
||||
Object? codeVerifier = _Undefined,
|
||||
Object? callerId = _Undefined,
|
||||
Object? caller = _Undefined,
|
||||
Object? subject = _Undefined,
|
||||
DateTime? expiresAt,
|
||||
}) {
|
||||
return OidcAttempt(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
provider: provider ?? this.provider,
|
||||
state: state ?? this.state,
|
||||
nonce: nonce ?? this.nonce,
|
||||
codeVerifier: codeVerifier is String? ? codeVerifier : this.codeVerifier,
|
||||
callerId: callerId is _is.UuidValue? ? callerId : this.callerId,
|
||||
caller: caller is _iacs.AuthUser? ? caller : this.caller?.copyWith(),
|
||||
subject: subject is String? ? subject : this.subject,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OidcAttemptUpdateTable extends _is.UpdateTable<OidcAttemptTable> {
|
||||
OidcAttemptUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<String, String> provider(String value) => _is.ColumnValue(
|
||||
table.provider,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> state(String value) => _is.ColumnValue(
|
||||
table.state,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> nonce(String value) => _is.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> codeVerifier(String? value) =>
|
||||
_is.ColumnValue(
|
||||
table.codeVerifier,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> callerId(
|
||||
_is.UuidValue? value,
|
||||
) => _is.ColumnValue(
|
||||
table.callerId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> subject(String? value) => _is.ColumnValue(
|
||||
table.subject,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> expiresAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.expiresAt,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class OidcAttemptTable extends _is.Table<_is.UuidValue?> {
|
||||
OidcAttemptTable({super.tableRelation})
|
||||
: super(tableName: 'halkyon_oidc_attempt') {
|
||||
updateTable = OidcAttemptUpdateTable(this);
|
||||
provider = _is.ColumnString(
|
||||
'provider',
|
||||
this,
|
||||
);
|
||||
state = _is.ColumnString(
|
||||
'state',
|
||||
this,
|
||||
);
|
||||
nonce = _is.ColumnString(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
codeVerifier = _is.ColumnString(
|
||||
'codeVerifier',
|
||||
this,
|
||||
);
|
||||
callerId = _is.ColumnUuid(
|
||||
'callerId',
|
||||
this,
|
||||
);
|
||||
subject = _is.ColumnString(
|
||||
'subject',
|
||||
this,
|
||||
);
|
||||
expiresAt = _is.ColumnDateTime(
|
||||
'expiresAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final OidcAttemptUpdateTable updateTable;
|
||||
|
||||
/// The provider, by its key (`OidcProvider.key`).
|
||||
late final _is.ColumnString provider;
|
||||
|
||||
/// The state sent with the person, which comes back with the code.
|
||||
late final _is.ColumnString state;
|
||||
|
||||
/// The nonce the provider's ID token must carry.
|
||||
late final _is.ColumnString nonce;
|
||||
|
||||
/// PKCE's code verifier; null for a provider without PKCE.
|
||||
late final _is.ColumnString codeVerifier;
|
||||
|
||||
late final _is.ColumnUuid callerId;
|
||||
|
||||
/// Who began it signed in, to have the identity linked to; null for a
|
||||
/// sign-in.
|
||||
_iacs.AuthUserTable? _caller;
|
||||
|
||||
/// The ID the provider proved, for an attempt kept after it was verified;
|
||||
/// null before.
|
||||
late final _is.ColumnString subject;
|
||||
|
||||
/// Until when it can be ended, or taken once verified.
|
||||
late final _is.ColumnDateTime expiresAt;
|
||||
|
||||
_iacs.AuthUserTable get caller {
|
||||
if (_caller != null) return _caller!;
|
||||
_caller = _is.createRelationTable(
|
||||
relationFieldName: 'caller',
|
||||
field: OidcAttempt.t.callerId,
|
||||
foreignField: _iacs.AuthUser.t.id,
|
||||
tableRelation: tableRelation,
|
||||
createTable: (foreignTableRelation) =>
|
||||
_iacs.AuthUserTable(tableRelation: foreignTableRelation),
|
||||
);
|
||||
return _caller!;
|
||||
}
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
provider,
|
||||
state,
|
||||
nonce,
|
||||
codeVerifier,
|
||||
callerId,
|
||||
subject,
|
||||
expiresAt,
|
||||
];
|
||||
|
||||
@override
|
||||
_is.Table? getRelationTable(String relationField) {
|
||||
if (relationField == 'caller') {
|
||||
return caller;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class OidcAttemptInclude extends _is.IncludeObject {
|
||||
OidcAttemptInclude._({_iacs.AuthUserInclude? caller}) {
|
||||
_caller = caller;
|
||||
}
|
||||
|
||||
_iacs.AuthUserInclude? _caller;
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {'caller': _caller};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => OidcAttempt.t;
|
||||
}
|
||||
|
||||
class OidcAttemptIncludeList extends _is.IncludeList {
|
||||
OidcAttemptIncludeList._({
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(OidcAttempt.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => OidcAttempt.t;
|
||||
}
|
||||
|
||||
class OidcAttemptRepository {
|
||||
const OidcAttemptRepository._();
|
||||
|
||||
final attachRow = const OidcAttemptAttachRowRepository._();
|
||||
|
||||
final detachRow = const OidcAttemptDetachRowRepository._();
|
||||
|
||||
/// Returns a list of [OidcAttempt]s matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order of the items use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// The maximum number of items can be set by [limit]. If no limit is set,
|
||||
/// all items matching the query will be returned.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which [limit] (or all)
|
||||
/// items are read from the database.
|
||||
///
|
||||
/// ```dart
|
||||
/// var persons = await Persons.db.find(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.firstName,
|
||||
/// limit: 100,
|
||||
/// );
|
||||
/// ```
|
||||
Future<List<OidcAttempt>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
OidcAttemptInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<OidcAttempt>(
|
||||
where: where?.call(OidcAttempt.t),
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [OidcAttempt] matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which the next one will be picked.
|
||||
///
|
||||
/// ```dart
|
||||
/// var youngestPerson = await Persons.db.findFirstRow(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.age,
|
||||
/// );
|
||||
/// ```
|
||||
Future<OidcAttempt?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
OidcAttemptInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<OidcAttempt>(
|
||||
where: where?.call(OidcAttempt.t),
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [OidcAttempt] by its [id] or null if no such row exists.
|
||||
Future<OidcAttempt?> findById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
_is.Transaction? transaction,
|
||||
OidcAttemptInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<OidcAttempt>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [OidcAttempt]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [OidcAttempt]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// insert, none of the rows will be inserted.
|
||||
///
|
||||
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
|
||||
/// rows are silently skipped, and only the successfully inserted rows are
|
||||
/// returned.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the inserted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcAttempt> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<OidcAttempt>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [OidcAttempt] and returns the inserted row.
|
||||
///
|
||||
/// The returned [OidcAttempt] will have its `id` field set.
|
||||
Future<OidcAttempt> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<OidcAttempt>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [OidcAttempt]s in the list and returns the resulting rows.
|
||||
///
|
||||
/// If a row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated with the new values. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies to rows matching the
|
||||
/// given expression. Conflicting rows that don't match are skipped and not
|
||||
/// returned, so the resulting list may be shorter than [rows].
|
||||
///
|
||||
/// The returned [OidcAttempt]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails,
|
||||
/// none of the rows will be affected.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the resulting rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcAttempt> rows, {
|
||||
required _is.ColumnSelections<OidcAttemptTable> conflictColumns,
|
||||
_is.ColumnSelections<OidcAttemptTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<OidcAttempt>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(OidcAttempt.t),
|
||||
updateColumns: updateColumns?.call(OidcAttempt.t),
|
||||
updateWhere: updateWhere?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [OidcAttempt] and returns the resulting row.
|
||||
///
|
||||
/// If the row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies when the existing
|
||||
/// row matches the expression. Returns `null` if no row was affected — for
|
||||
/// example when [updateWhere] does not match the conflicting row.
|
||||
///
|
||||
/// The returned [OidcAttempt] will have its `id` field set.
|
||||
Future<OidcAttempt?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt row, {
|
||||
required _is.ColumnSelections<OidcAttemptTable> conflictColumns,
|
||||
_is.ColumnSelections<OidcAttemptTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<OidcAttempt>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(OidcAttempt.t),
|
||||
updateColumns: updateColumns?.call(OidcAttempt.t),
|
||||
updateWhere: updateWhere?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [OidcAttempt]s in the list and returns the updated rows. If
|
||||
/// [columns] is provided, only those columns will be updated. Defaults to
|
||||
/// all columns.
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// update, none of the rows will be updated.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcAttempt> rows, {
|
||||
_is.ColumnSelections<OidcAttemptTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<OidcAttempt>(
|
||||
rows,
|
||||
columns: columns?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [OidcAttempt]. The row needs to have its id set.
|
||||
/// Optionally, a list of [columns] can be provided to only update those
|
||||
/// columns. Defaults to all columns.
|
||||
Future<OidcAttempt> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt row, {
|
||||
_is.ColumnSelections<OidcAttemptTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<OidcAttempt>(
|
||||
row,
|
||||
columns: columns?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [OidcAttempt] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<OidcAttempt?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
required _is.ColumnValueListBuilder<OidcAttemptUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<OidcAttempt>(
|
||||
id,
|
||||
columnValues: columnValues(OidcAttempt.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [OidcAttempt]s matching the [where] expression with the specified [columnValues].
|
||||
/// Returns the list of updated rows.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<OidcAttemptUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<OidcAttemptTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<OidcAttempt>(
|
||||
columnValues: columnValues(OidcAttempt.t.updateTable),
|
||||
where: where(OidcAttempt.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [OidcAttempt]s in the list and returns the deleted rows.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fail to
|
||||
/// be deleted, none of the rows will be deleted.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcAttempt> rows, {
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<OidcAttempt>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [OidcAttempt].
|
||||
Future<OidcAttempt> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<OidcAttempt>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all rows matching the [where] expression.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcAttempt>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<OidcAttemptTable> where,
|
||||
_is.OrderByBuilder<OidcAttemptTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcAttemptTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<OidcAttempt>(
|
||||
where: where(OidcAttempt.t),
|
||||
orderBy: orderBy?.call(OidcAttempt.t),
|
||||
orderByList: orderByList?.call(OidcAttempt.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Counts the number of rows matching the [where] expression. If omitted,
|
||||
/// will return the count of all rows in the table.
|
||||
Future<int> count(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcAttemptTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<OidcAttempt>(
|
||||
where: where?.call(OidcAttempt.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [OidcAttempt] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<OidcAttemptTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<OidcAttempt>(
|
||||
where: where(OidcAttempt.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OidcAttemptAttachRowRepository {
|
||||
const OidcAttemptAttachRowRepository._();
|
||||
|
||||
/// Creates a relation between the given [OidcAttempt] and [AuthUser]
|
||||
/// by setting the [OidcAttempt]'s foreign key `callerId` to refer to the [AuthUser].
|
||||
Future<void> caller(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt oidcAttempt,
|
||||
_iacs.AuthUser caller, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
if (oidcAttempt.id == null) {
|
||||
throw ArgumentError.notNull('oidcAttempt.id');
|
||||
}
|
||||
if (caller.id == null) {
|
||||
throw ArgumentError.notNull('caller.id');
|
||||
}
|
||||
|
||||
var $oidcAttempt = oidcAttempt.copyWith(callerId: caller.id);
|
||||
await session.db.updateRow<OidcAttempt>(
|
||||
$oidcAttempt,
|
||||
columns: [OidcAttempt.t.callerId],
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OidcAttemptDetachRowRepository {
|
||||
const OidcAttemptDetachRowRepository._();
|
||||
|
||||
/// Detaches the relation between this [OidcAttempt] and the [AuthUser] set in `caller`
|
||||
/// by setting the [OidcAttempt]'s foreign key `callerId` to `null`.
|
||||
///
|
||||
/// This removes the association between the two models without deleting
|
||||
/// the related record.
|
||||
Future<void> caller(
|
||||
_is.DatabaseSession session,
|
||||
OidcAttempt oidcAttempt, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
if (oidcAttempt.id == null) {
|
||||
throw ArgumentError.notNull('oidcAttempt.id');
|
||||
}
|
||||
|
||||
var $oidcAttempt = oidcAttempt.copyWith(callerId: null);
|
||||
await session.db.updateRow<OidcAttempt>(
|
||||
$oidcAttempt,
|
||||
columns: [OidcAttempt.t.callerId],
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,742 @@
|
|||
/* 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_null_comparison
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_oidc_server/src/generated/protocol.dart' as _i7me1mu7;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
|
||||
/// A person's identity at an OpenID Connect provider: the provider's ID of
|
||||
/// them (`sub`) and nothing else of what it says (ADR 0023, 3).
|
||||
abstract class OidcIdentity
|
||||
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
|
||||
OidcIdentity._({
|
||||
this.id,
|
||||
required this.authUserId,
|
||||
this.authUser,
|
||||
required this.provider,
|
||||
required this.subject,
|
||||
DateTime? createdAt,
|
||||
}) : createdAt = createdAt ?? DateTime.now();
|
||||
|
||||
factory OidcIdentity({
|
||||
_is.UuidValue? id,
|
||||
required _is.UuidValue authUserId,
|
||||
_iacs.AuthUser? authUser,
|
||||
required String provider,
|
||||
required String subject,
|
||||
DateTime? createdAt,
|
||||
}) = _OidcIdentityImpl;
|
||||
|
||||
factory OidcIdentity.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return OidcIdentity(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
authUserId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['authUserId'],
|
||||
),
|
||||
authUser: jsonSerialization['authUser'] == null
|
||||
? null
|
||||
: _i7me1mu7.Protocol().deserialize<_iacs.AuthUser>(
|
||||
jsonSerialization['authUser'],
|
||||
),
|
||||
provider: jsonSerialization['provider'] as String,
|
||||
subject: jsonSerialization['subject'] as String,
|
||||
createdAt: jsonSerialization['createdAt'] == null
|
||||
? null
|
||||
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['createdAt']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = OidcIdentityTable();
|
||||
|
||||
static const db = OidcIdentityRepository._();
|
||||
|
||||
@override
|
||||
_is.UuidValue? id;
|
||||
|
||||
_is.UuidValue authUserId;
|
||||
|
||||
/// The person, who goes with their identities.
|
||||
_iacs.AuthUser? authUser;
|
||||
|
||||
/// The provider, by the key the host named it with (`OidcProvider.key`).
|
||||
String provider;
|
||||
|
||||
/// The provider's ID of the person, its ID token's `sub`, as it came.
|
||||
String subject;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [OidcIdentity]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
OidcIdentity copyWith({
|
||||
_is.UuidValue? id,
|
||||
_is.UuidValue? authUserId,
|
||||
_iacs.AuthUser? authUser,
|
||||
String? provider,
|
||||
String? subject,
|
||||
DateTime? createdAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcIdentity',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
'authUserId': authUserId.toJson(),
|
||||
if (authUser != null) 'authUser': authUser?.toJson(),
|
||||
'provider': provider,
|
||||
'subject': subject,
|
||||
'createdAt': createdAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static OidcIdentityInclude include({_iacs.AuthUserInclude? authUser}) {
|
||||
return OidcIdentityInclude._(authUser: authUser);
|
||||
}
|
||||
|
||||
static OidcIdentityIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
OidcIdentityInclude? include,
|
||||
}) {
|
||||
return OidcIdentityIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _OidcIdentityImpl extends OidcIdentity {
|
||||
_OidcIdentityImpl({
|
||||
_is.UuidValue? id,
|
||||
required _is.UuidValue authUserId,
|
||||
_iacs.AuthUser? authUser,
|
||||
required String provider,
|
||||
required String subject,
|
||||
DateTime? createdAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
authUserId: authUserId,
|
||||
authUser: authUser,
|
||||
provider: provider,
|
||||
subject: subject,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [OidcIdentity]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
OidcIdentity copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? authUserId,
|
||||
Object? authUser = _Undefined,
|
||||
String? provider,
|
||||
String? subject,
|
||||
DateTime? createdAt,
|
||||
}) {
|
||||
return OidcIdentity(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
authUserId: authUserId ?? this.authUserId,
|
||||
authUser: authUser is _iacs.AuthUser?
|
||||
? authUser
|
||||
: this.authUser?.copyWith(),
|
||||
provider: provider ?? this.provider,
|
||||
subject: subject ?? this.subject,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OidcIdentityUpdateTable extends _is.UpdateTable<OidcIdentityTable> {
|
||||
OidcIdentityUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
|
||||
_is.UuidValue value,
|
||||
) => _is.ColumnValue(
|
||||
table.authUserId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> provider(String value) => _is.ColumnValue(
|
||||
table.provider,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> subject(String value) => _is.ColumnValue(
|
||||
table.subject,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.createdAt,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class OidcIdentityTable extends _is.Table<_is.UuidValue?> {
|
||||
OidcIdentityTable({super.tableRelation})
|
||||
: super(tableName: 'halkyon_oidc_identity') {
|
||||
updateTable = OidcIdentityUpdateTable(this);
|
||||
authUserId = _is.ColumnUuid(
|
||||
'authUserId',
|
||||
this,
|
||||
);
|
||||
provider = _is.ColumnString(
|
||||
'provider',
|
||||
this,
|
||||
);
|
||||
subject = _is.ColumnString(
|
||||
'subject',
|
||||
this,
|
||||
);
|
||||
createdAt = _is.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final OidcIdentityUpdateTable updateTable;
|
||||
|
||||
late final _is.ColumnUuid authUserId;
|
||||
|
||||
/// The person, who goes with their identities.
|
||||
_iacs.AuthUserTable? _authUser;
|
||||
|
||||
/// The provider, by the key the host named it with (`OidcProvider.key`).
|
||||
late final _is.ColumnString provider;
|
||||
|
||||
/// The provider's ID of the person, its ID token's `sub`, as it came.
|
||||
late final _is.ColumnString subject;
|
||||
|
||||
late final _is.ColumnDateTime createdAt;
|
||||
|
||||
_iacs.AuthUserTable get authUser {
|
||||
if (_authUser != null) return _authUser!;
|
||||
_authUser = _is.createRelationTable(
|
||||
relationFieldName: 'authUser',
|
||||
field: OidcIdentity.t.authUserId,
|
||||
foreignField: _iacs.AuthUser.t.id,
|
||||
tableRelation: tableRelation,
|
||||
createTable: (foreignTableRelation) =>
|
||||
_iacs.AuthUserTable(tableRelation: foreignTableRelation),
|
||||
);
|
||||
return _authUser!;
|
||||
}
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
authUserId,
|
||||
provider,
|
||||
subject,
|
||||
createdAt,
|
||||
];
|
||||
|
||||
@override
|
||||
_is.Table? getRelationTable(String relationField) {
|
||||
if (relationField == 'authUser') {
|
||||
return authUser;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class OidcIdentityInclude extends _is.IncludeObject {
|
||||
OidcIdentityInclude._({_iacs.AuthUserInclude? authUser}) {
|
||||
_authUser = authUser;
|
||||
}
|
||||
|
||||
_iacs.AuthUserInclude? _authUser;
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {'authUser': _authUser};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => OidcIdentity.t;
|
||||
}
|
||||
|
||||
class OidcIdentityIncludeList extends _is.IncludeList {
|
||||
OidcIdentityIncludeList._({
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(OidcIdentity.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => OidcIdentity.t;
|
||||
}
|
||||
|
||||
class OidcIdentityRepository {
|
||||
const OidcIdentityRepository._();
|
||||
|
||||
final attachRow = const OidcIdentityAttachRowRepository._();
|
||||
|
||||
/// Returns a list of [OidcIdentity]s matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order of the items use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// The maximum number of items can be set by [limit]. If no limit is set,
|
||||
/// all items matching the query will be returned.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which [limit] (or all)
|
||||
/// items are read from the database.
|
||||
///
|
||||
/// ```dart
|
||||
/// var persons = await Persons.db.find(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.firstName,
|
||||
/// limit: 100,
|
||||
/// );
|
||||
/// ```
|
||||
Future<List<OidcIdentity>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
OidcIdentityInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<OidcIdentity>(
|
||||
where: where?.call(OidcIdentity.t),
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [OidcIdentity] matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which the next one will be picked.
|
||||
///
|
||||
/// ```dart
|
||||
/// var youngestPerson = await Persons.db.findFirstRow(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.age,
|
||||
/// );
|
||||
/// ```
|
||||
Future<OidcIdentity?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
OidcIdentityInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<OidcIdentity>(
|
||||
where: where?.call(OidcIdentity.t),
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [OidcIdentity] by its [id] or null if no such row exists.
|
||||
Future<OidcIdentity?> findById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
_is.Transaction? transaction,
|
||||
OidcIdentityInclude? include,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<OidcIdentity>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
include: include,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [OidcIdentity]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [OidcIdentity]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// insert, none of the rows will be inserted.
|
||||
///
|
||||
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
|
||||
/// rows are silently skipped, and only the successfully inserted rows are
|
||||
/// returned.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the inserted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcIdentity> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<OidcIdentity>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [OidcIdentity] and returns the inserted row.
|
||||
///
|
||||
/// The returned [OidcIdentity] will have its `id` field set.
|
||||
Future<OidcIdentity> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcIdentity row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<OidcIdentity>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [OidcIdentity]s in the list and returns the resulting rows.
|
||||
///
|
||||
/// If a row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated with the new values. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies to rows matching the
|
||||
/// given expression. Conflicting rows that don't match are skipped and not
|
||||
/// returned, so the resulting list may be shorter than [rows].
|
||||
///
|
||||
/// The returned [OidcIdentity]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails,
|
||||
/// none of the rows will be affected.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the resulting rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcIdentity> rows, {
|
||||
required _is.ColumnSelections<OidcIdentityTable> conflictColumns,
|
||||
_is.ColumnSelections<OidcIdentityTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<OidcIdentity>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(OidcIdentity.t),
|
||||
updateColumns: updateColumns?.call(OidcIdentity.t),
|
||||
updateWhere: updateWhere?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [OidcIdentity] and returns the resulting row.
|
||||
///
|
||||
/// If the row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies when the existing
|
||||
/// row matches the expression. Returns `null` if no row was affected — for
|
||||
/// example when [updateWhere] does not match the conflicting row.
|
||||
///
|
||||
/// The returned [OidcIdentity] will have its `id` field set.
|
||||
Future<OidcIdentity?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcIdentity row, {
|
||||
required _is.ColumnSelections<OidcIdentityTable> conflictColumns,
|
||||
_is.ColumnSelections<OidcIdentityTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<OidcIdentity>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(OidcIdentity.t),
|
||||
updateColumns: updateColumns?.call(OidcIdentity.t),
|
||||
updateWhere: updateWhere?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [OidcIdentity]s in the list and returns the updated rows. If
|
||||
/// [columns] is provided, only those columns will be updated. Defaults to
|
||||
/// all columns.
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// update, none of the rows will be updated.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcIdentity> rows, {
|
||||
_is.ColumnSelections<OidcIdentityTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<OidcIdentity>(
|
||||
rows,
|
||||
columns: columns?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [OidcIdentity]. The row needs to have its id set.
|
||||
/// Optionally, a list of [columns] can be provided to only update those
|
||||
/// columns. Defaults to all columns.
|
||||
Future<OidcIdentity> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcIdentity row, {
|
||||
_is.ColumnSelections<OidcIdentityTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<OidcIdentity>(
|
||||
row,
|
||||
columns: columns?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [OidcIdentity] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<OidcIdentity?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
required _is.ColumnValueListBuilder<OidcIdentityUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<OidcIdentity>(
|
||||
id,
|
||||
columnValues: columnValues(OidcIdentity.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [OidcIdentity]s matching the [where] expression with the specified [columnValues].
|
||||
/// Returns the list of updated rows.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<OidcIdentityUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<OidcIdentityTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<OidcIdentity>(
|
||||
columnValues: columnValues(OidcIdentity.t.updateTable),
|
||||
where: where(OidcIdentity.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [OidcIdentity]s in the list and returns the deleted rows.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fail to
|
||||
/// be deleted, none of the rows will be deleted.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<OidcIdentity> rows, {
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<OidcIdentity>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [OidcIdentity].
|
||||
Future<OidcIdentity> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
OidcIdentity row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<OidcIdentity>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all rows matching the [where] expression.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<OidcIdentity>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<OidcIdentityTable> where,
|
||||
_is.OrderByBuilder<OidcIdentityTable>? orderBy,
|
||||
_is.OrderByListBuilder<OidcIdentityTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<OidcIdentity>(
|
||||
where: where(OidcIdentity.t),
|
||||
orderBy: orderBy?.call(OidcIdentity.t),
|
||||
orderByList: orderByList?.call(OidcIdentity.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Counts the number of rows matching the [where] expression. If omitted,
|
||||
/// will return the count of all rows in the table.
|
||||
Future<int> count(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<OidcIdentityTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<OidcIdentity>(
|
||||
where: where?.call(OidcIdentity.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [OidcIdentity] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<OidcIdentityTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<OidcIdentity>(
|
||||
where: where(OidcIdentity.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OidcIdentityAttachRowRepository {
|
||||
const OidcIdentityAttachRowRepository._();
|
||||
|
||||
/// Creates a relation between the given [OidcIdentity] and [AuthUser]
|
||||
/// by setting the [OidcIdentity]'s foreign key `authUserId` to refer to the [AuthUser].
|
||||
Future<void> authUser(
|
||||
_is.DatabaseSession session,
|
||||
OidcIdentity oidcIdentity,
|
||||
_iacs.AuthUser authUser, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
if (oidcIdentity.id == null) {
|
||||
throw ArgumentError.notNull('oidcIdentity.id');
|
||||
}
|
||||
if (authUser.id == null) {
|
||||
throw ArgumentError.notNull('authUser.id');
|
||||
}
|
||||
|
||||
var $oidcIdentity = oidcIdentity.copyWith(authUserId: authUser.id);
|
||||
await session.db.updateRow<OidcIdentity>(
|
||||
$oidcIdentity,
|
||||
columns: [OidcIdentity.t.authUserId],
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/* 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: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// Why a sign-in with an OpenID Connect provider was refused.
|
||||
enum OidcRefusal implements _is.SerializableModel {
|
||||
/// The host names no provider by this key.
|
||||
unknownProvider,
|
||||
|
||||
/// No attempt has this ID: none began, it was ended already, or its time
|
||||
/// ran out.
|
||||
unknownAttempt,
|
||||
|
||||
/// The attempt was begun by someone else, or signed in where this call is
|
||||
/// signed out, or the other way round.
|
||||
wrongCaller,
|
||||
|
||||
/// The provider refused the code, or its answer didn't prove anyone: a
|
||||
/// token not signed by its key, for another client or attempt, expired.
|
||||
rejected,
|
||||
|
||||
/// The provider couldn't be reached, or answered nothing readable; trying
|
||||
/// again later may work.
|
||||
providerUnavailable,
|
||||
|
||||
/// The identity is another person's.
|
||||
takenByAnotherPerson,
|
||||
|
||||
/// The person has another identity at this provider.
|
||||
otherIdentityLinked,
|
||||
|
||||
/// Too many sign-ins were begun from this network; later.
|
||||
tooManyAttempts;
|
||||
|
||||
static OidcRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'unknownProvider':
|
||||
return OidcRefusal.unknownProvider;
|
||||
case 'unknownAttempt':
|
||||
return OidcRefusal.unknownAttempt;
|
||||
case 'wrongCaller':
|
||||
return OidcRefusal.wrongCaller;
|
||||
case 'rejected':
|
||||
return OidcRefusal.rejected;
|
||||
case 'providerUnavailable':
|
||||
return OidcRefusal.providerUnavailable;
|
||||
case 'takenByAnotherPerson':
|
||||
return OidcRefusal.takenByAnotherPerson;
|
||||
case 'otherIdentityLinked':
|
||||
return OidcRefusal.otherIdentityLinked;
|
||||
case 'tooManyAttempts':
|
||||
return OidcRefusal.tooManyAttempts;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "OidcRefusal"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/* 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: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'oidc_refusal.dart' as _i8gvcysd;
|
||||
|
||||
/// A sign-in with an OpenID Connect provider was refused; nothing changed.
|
||||
abstract class OidcRefusedException
|
||||
implements
|
||||
_is.SerializableException,
|
||||
_is.SerializableModel,
|
||||
_is.ProtocolSerialization {
|
||||
OidcRefusedException._({required this.reason});
|
||||
|
||||
factory OidcRefusedException({required _i8gvcysd.OidcRefusal reason}) =
|
||||
_OidcRefusedExceptionImpl;
|
||||
|
||||
factory OidcRefusedException.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return OidcRefusedException(
|
||||
reason: _i8gvcysd.OidcRefusal.fromJson(
|
||||
(jsonSerialization['reason'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Why it was refused.
|
||||
_i8gvcysd.OidcRefusal reason;
|
||||
|
||||
/// Returns a shallow copy of this [OidcRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
OidcRefusedException copyWith({_i8gvcysd.OidcRefusal? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OidcRefusedException(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _OidcRefusedExceptionImpl extends OidcRefusedException {
|
||||
_OidcRefusedExceptionImpl({required _i8gvcysd.OidcRefusal reason})
|
||||
: super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [OidcRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
OidcRefusedException copyWith({_i8gvcysd.OidcRefusal? reason}) {
|
||||
return OidcRefusedException(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
114
packages/halkyon_oidc_server/lib/src/generated/oidc_start.dart
Normal file
114
packages/halkyon_oidc_server/lib/src/generated/oidc_start.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* 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: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// What a device needs to send a person to their provider: the attempt it
|
||||
/// ends the sign-in with, which only the device holds, and where to go.
|
||||
abstract class OidcStart
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
OidcStart._({
|
||||
required this.attempt,
|
||||
required this.authorizationUrl,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
factory OidcStart({
|
||||
required _is.UuidValue attempt,
|
||||
required Uri authorizationUrl,
|
||||
required String state,
|
||||
}) = _OidcStartImpl;
|
||||
|
||||
factory OidcStart.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return OidcStart(
|
||||
attempt: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['attempt'],
|
||||
),
|
||||
authorizationUrl: _is.UriJsonExtension.fromJson(
|
||||
jsonSerialization['authorizationUrl'],
|
||||
),
|
||||
state: jsonSerialization['state'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// The attempt's ID, which ends it (`verify`); never shown, never sent
|
||||
/// anywhere but to this server.
|
||||
_is.UuidValue attempt;
|
||||
|
||||
/// The provider's authorization address, with everything it needs.
|
||||
Uri authorizationUrl;
|
||||
|
||||
/// The state the provider sends back with the code, which the device
|
||||
/// compares with what comes back before it goes on.
|
||||
String state;
|
||||
|
||||
/// Returns a shallow copy of this [OidcStart]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
OidcStart copyWith({
|
||||
_is.UuidValue? attempt,
|
||||
Uri? authorizationUrl,
|
||||
String? state,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcStart',
|
||||
'attempt': attempt.toJson(),
|
||||
'authorizationUrl': authorizationUrl.toJson(),
|
||||
'state': state,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_oidc.OidcStart',
|
||||
'attempt': attempt.toJson(),
|
||||
'authorizationUrl': authorizationUrl.toJson(),
|
||||
'state': state,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _OidcStartImpl extends OidcStart {
|
||||
_OidcStartImpl({
|
||||
required _is.UuidValue attempt,
|
||||
required Uri authorizationUrl,
|
||||
required String state,
|
||||
}) : super._(
|
||||
attempt: attempt,
|
||||
authorizationUrl: authorizationUrl,
|
||||
state: state,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [OidcStart]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
OidcStart copyWith({
|
||||
_is.UuidValue? attempt,
|
||||
Uri? authorizationUrl,
|
||||
String? state,
|
||||
}) {
|
||||
return OidcStart(
|
||||
attempt: attempt ?? this.attempt,
|
||||
authorizationUrl: authorizationUrl ?? this.authorizationUrl,
|
||||
state: state ?? this.state,
|
||||
);
|
||||
}
|
||||
}
|
||||
481
packages/halkyon_oidc_server/lib/src/generated/protocol.dart
Normal file
481
packages/halkyon_oidc_server/lib/src/generated/protocol.dart
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
/* 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/protocol.dart' as _isp;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
import 'oidc_attempt.dart' as _ihieazd8;
|
||||
import 'oidc_identity.dart' as _iif3vjzw;
|
||||
import 'oidc_refusal.dart' as _i8gvcysd;
|
||||
import 'oidc_refused_exception.dart' as _i70wwg4b;
|
||||
import 'oidc_start.dart' as _i4fk0f61;
|
||||
export 'oidc_attempt.dart';
|
||||
export 'oidc_identity.dart';
|
||||
export 'oidc_refusal.dart';
|
||||
export 'oidc_refused_exception.dart';
|
||||
export 'oidc_start.dart';
|
||||
|
||||
class Protocol extends _is.DatabaseSerializationManager {
|
||||
Protocol._();
|
||||
|
||||
factory Protocol() => _instance;
|
||||
|
||||
static final Protocol _instance = Protocol._();
|
||||
|
||||
final Set<_is.SerializationManager> _hostProtocols = {};
|
||||
|
||||
static List<_isp.TableDefinition> get targetTableDefinitions => [
|
||||
_isp.TableDefinition(
|
||||
name: 'halkyon_oidc_attempt',
|
||||
dartName: 'OidcAttempt',
|
||||
schema: 'public',
|
||||
module: 'halkyon_oidc',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'provider',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'state',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'nonce',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'codeVerifier',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'callerId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: true,
|
||||
dartType: 'UuidValue?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'subject',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'expiresAt',
|
||||
columnType: _isp.ColumnType.timestampWithoutTimeZone,
|
||||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
constraintName: 'halkyon_oidc_attempt_fk_0',
|
||||
columns: ['callerId'],
|
||||
referenceTable: 'serverpod_auth_core_user',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isp.ForeignKeyAction.noAction,
|
||||
onDelete: _isp.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'halkyon_oidc_attempt_expires_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'expiresAt',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'halkyon_oidc_identity',
|
||||
dartName: 'OidcIdentity',
|
||||
schema: 'public',
|
||||
module: 'halkyon_oidc',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random_v7',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'authUserId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'provider',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'subject',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'createdAt',
|
||||
columnType: _isp.ColumnType.timestampWithoutTimeZone,
|
||||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
constraintName: 'halkyon_oidc_identity_fk_0',
|
||||
columns: ['authUserId'],
|
||||
referenceTable: 'serverpod_auth_core_user',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isp.ForeignKeyAction.noAction,
|
||||
onDelete: _isp.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'halkyon_oidc_identity_subject_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'provider',
|
||||
),
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'subject',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'halkyon_oidc_identity_person_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'authUserId',
|
||||
),
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'provider',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
..._iais.Protocol.targetTableDefinitions,
|
||||
..._iacs.Protocol.targetTableDefinitions,
|
||||
];
|
||||
|
||||
void registerHostProtocol(
|
||||
String projectName,
|
||||
_is.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 _is.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 == _ihieazd8.OidcAttempt) {
|
||||
return _ihieazd8.OidcAttempt.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iif3vjzw.OidcIdentity) {
|
||||
return _iif3vjzw.OidcIdentity.fromJson(data) as 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 == _is.getType<_ihieazd8.OidcAttempt?>()) {
|
||||
return (data != null ? _ihieazd8.OidcAttempt.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_iif3vjzw.OidcIdentity?>()) {
|
||||
return (data != null ? _iif3vjzw.OidcIdentity.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_i8gvcysd.OidcRefusal?>()) {
|
||||
return (data != null ? _i8gvcysd.OidcRefusal.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_i70wwg4b.OidcRefusedException?>()) {
|
||||
return (data != null
|
||||
? _i70wwg4b.OidcRefusedException.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_i4fk0f61.OidcStart?>()) {
|
||||
return (data != null ? _i4fk0f61.OidcStart.fromJson(data) : null) as T;
|
||||
}
|
||||
try {
|
||||
return _iais.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _isp.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
return super.deserialize<T>(data, t);
|
||||
}
|
||||
|
||||
static String? getClassNameForType(Type type) {
|
||||
return switch (type) {
|
||||
_ihieazd8.OidcAttempt => 'OidcAttempt',
|
||||
_iif3vjzw.OidcIdentity => 'OidcIdentity',
|
||||
_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 _ihieazd8.OidcAttempt():
|
||||
return 'OidcAttempt';
|
||||
case _iif3vjzw.OidcIdentity():
|
||||
return 'OidcIdentity';
|
||||
case _i8gvcysd.OidcRefusal():
|
||||
return 'OidcRefusal';
|
||||
case _i70wwg4b.OidcRefusedException():
|
||||
return 'OidcRefusedException';
|
||||
case _i4fk0f61.OidcStart():
|
||||
return 'OidcStart';
|
||||
}
|
||||
className = _isp.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.') ? className : 'serverpod.$className';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic deserializeByClassName(Map<String, dynamic> data) {
|
||||
var dataClassName = data['className'];
|
||||
if (dataClassName is! String) {
|
||||
return super.deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName == 'OidcAttempt') {
|
||||
return deserialize<_ihieazd8.OidcAttempt>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'OidcIdentity') {
|
||||
return deserialize<_iif3vjzw.OidcIdentity>(data['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']);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod.')) {
|
||||
data['className'] = dataClassName.substring(10);
|
||||
return _isp.Protocol().deserializeByClassName(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
|
||||
? _is.SerializationManager.toEncodableForProtocol(wrapped)
|
||||
: _is.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 _is.DeserializationClassNameNotFoundException catch (_) {}
|
||||
}
|
||||
}
|
||||
return deserializeByClassName(value);
|
||||
}
|
||||
|
||||
@override
|
||||
_is.Table? getTableForType(Type t) {
|
||||
{
|
||||
var table = _iais.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _iacs.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _isp.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
switch (t) {
|
||||
case _ihieazd8.OidcAttempt:
|
||||
return _ihieazd8.OidcAttempt.t;
|
||||
case _iif3vjzw.OidcIdentity:
|
||||
return _iif3vjzw.OidcIdentity.t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<_isp.TableDefinition> getTargetTableDefinitions() =>
|
||||
targetTableDefinitions;
|
||||
|
||||
@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 _iais.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
throw Exception('Unsupported record type ${record.runtimeType}');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue