Skip to content

Commit 2f22460

Browse files
authored
fix: enable sign in using enterprise sso [#248] (#259)
* fix: enable sign in using enterprise sso [#248] * fix: refactors [#248] * fix: changes requested in code review [#248]
1 parent 136ef83 commit 2f22460

21 files changed

+512
-345
lines changed

packages/clerk_auth/lib/src/clerk_api/api.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,19 @@ class Api with Logging {
121121
return Client.empty;
122122
}
123123

124+
/// Force-create a new [Client]
125+
Future<Client> resetClient() async {
126+
return await _fetchClient(method: HttpMethod.post);
127+
}
128+
124129
/// Creates a new [Client] object to manage sessions
125130
Future<Client> createClient() async {
126131
if (_tokenCache.hasClientToken) {
127132
final client = await currentClient();
128133
if (client.isNotEmpty) return client;
129134
}
130135

131-
return await _fetchClient(method: HttpMethod.post);
136+
return await resetClient();
132137
}
133138

134139
/// Gets a refreshed [Client] object from the back end

packages/clerk_auth/lib/src/clerk_auth/auth.dart

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ class Auth {
103103
bool get isSignedIn => user != null;
104104

105105
/// Are we currently signing in?
106-
bool get isSigningIn => signIn?.status.isActive == true;
106+
bool get isSigningIn => signIn != null;
107107

108108
/// Are we currently signing up?
109-
bool get isSigningUp => signUp?.status.isActive == true;
109+
bool get isSigningUp => signUp != null;
110110

111111
/// A method to be overridden by extension classes to cope with
112112
/// updating their systems when things change (e.g. the clerk_flutter
@@ -236,6 +236,13 @@ class Auth {
236236
update();
237237
}
238238

239+
/// Reset the current [Client]: clear any [SignUp] or [SignIn] object
240+
///
241+
Future<void> resetClient() async {
242+
client = await _api.resetClient();
243+
update();
244+
}
245+
239246
/// Refresh the current [Environment]
240247
///
241248
Future<void> refreshEnvironment() async {
@@ -283,12 +290,17 @@ class Auth {
283290
Future<void> oauthSignIn({
284291
required Strategy strategy,
285292
required Uri? redirect,
293+
String? identifier,
286294
}) async {
287295
final redirectUrl = redirect?.toString() ?? ClerkConstants.oauthRedirect;
288296
await _api
289-
.createSignIn(strategy: strategy, redirectUrl: redirectUrl)
297+
.createSignIn(
298+
strategy: strategy,
299+
identifier: identifier,
300+
redirectUrl: redirectUrl,
301+
)
290302
.then(_housekeeping);
291-
if (client.signIn case SignIn signIn) {
303+
if (client.signIn case SignIn signIn when signIn.hasVerification == false) {
292304
await _api
293305
.prepareSignIn(
294306
signIn,
@@ -363,8 +375,10 @@ class Auth {
363375
return;
364376
}
365377

366-
// Ensure we have a signIn object
367-
if (client.signIn == null) {
378+
// Ensure we have a signIn object for the current identifier
379+
if (client.signIn == null ||
380+
(identifier?.orNullIfEmpty is String &&
381+
identifier != client.signIn!.identifier)) {
368382
// if password and identifier been presented, we can immediately attempt
369383
// a sign in; if null they will be ignored
370384
await _api
@@ -377,22 +391,14 @@ class Auth {
377391
// We have signed in - possibly when creating the [SignIn] above
378392
break;
379393

380-
case SignIn signIn
381-
when signIn.status == Status.needsIdentifier && identifier is String:
382-
// if a password has been presented, we can immediately attempt a
383-
// sign in; if `password` is null it will be ignored
384-
await _api
385-
.createSignIn(identifier: identifier, password: password)
386-
.then(_housekeeping);
387-
388-
case SignIn signIn when strategy.isOauth && token is String:
394+
case SignIn signIn when strategy.isSSO && token is String:
389395
await _api
390396
.sendOauthToken(signIn, strategy: strategy, token: token)
391397
.then(_housekeeping);
392398

393399
case SignIn signIn
394400
when strategy.isPasswordResetter &&
395-
code is String &&
401+
code?.length == _codeLength &&
396402
password is String:
397403
await _api
398404
.attemptSignIn(
@@ -431,13 +437,13 @@ class Auth {
431437
return signInCompleter.future;
432438

433439
case SignIn signIn
434-
when signIn.status == Status.needsFirstFactor &&
435-
strategy == Strategy.password &&
440+
when signIn.status.needsFactor &&
441+
strategy.isPassword &&
436442
password is String:
437443
await _api
438444
.attemptSignIn(
439445
signIn,
440-
stage: Stage.first,
446+
stage: Stage.forStatus(signIn.status),
441447
strategy: Strategy.password,
442448
password: password,
443449
)
@@ -459,26 +465,6 @@ class Auth {
459465
stage: stage, strategy: strategy, code: code)
460466
.then(_housekeeping);
461467
}
462-
463-
case SignIn signIn when signIn.status.needsFactor:
464-
final stage = Stage.forStatus(signIn.status);
465-
await _api
466-
.prepareSignIn(signIn, stage: stage, strategy: strategy)
467-
.then(_housekeeping);
468-
await _api
469-
.attemptSignIn(signIn, stage: stage, strategy: strategy, code: code)
470-
.then(_housekeeping);
471-
472-
// No matching sign-in sequence, reset loading state
473-
default:
474-
final status = signIn?.status ?? Status.unknown;
475-
addError(
476-
AuthError(
477-
code: AuthErrorCode.signInError,
478-
message: 'Unsupported sign in attempt: {arg}',
479-
argument: status.name,
480-
),
481-
);
482468
}
483469

484470
update();

packages/clerk_auth/lib/src/models/client/sign_in.dart

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:clerk_auth/src/models/client/verification.dart';
66
import 'package:clerk_auth/src/models/enums.dart';
77
import 'package:clerk_auth/src/models/informative_to_string_mixin.dart';
88
import 'package:clerk_auth/src/models/status.dart';
9+
import 'package:clerk_auth/src/utils/extensions.dart';
910
import 'package:clerk_auth/src/utils/json_serialization_helpers.dart';
1011
import 'package:json_annotation/json_annotation.dart';
1112
import 'package:meta/meta.dart';
@@ -20,15 +21,15 @@ class SignIn with InformativeToStringMixin {
2021
const SignIn({
2122
required this.id,
2223
required this.status,
23-
required this.supportedIdentifiers,
24-
required this.identifier,
25-
required this.userData,
26-
required this.supportedFirstFactors,
27-
required this.firstFactorVerification,
28-
required this.supportedSecondFactors,
29-
required this.secondFactorVerification,
30-
required this.createdSessionId,
31-
required this.abandonAt,
24+
this.identifier,
25+
this.userData,
26+
this.supportedIdentifiers = const [],
27+
this.supportedFirstFactors = const [],
28+
this.firstFactorVerification,
29+
this.supportedSecondFactors = const [],
30+
this.secondFactorVerification,
31+
this.createdSessionId,
32+
this.abandonAt = DateTimeExt.epoch,
3233
});
3334

3435
/// id
@@ -67,6 +68,16 @@ class SignIn with InformativeToStringMixin {
6768
@JsonKey(defaultValue: [])
6869
final List<Factor> supportedSecondFactors;
6970

71+
/// Empty [SignIn]
72+
static const empty = SignIn(id: '~empty~', status: Status.unknown);
73+
74+
/// The currently most important verification
75+
Verification? get verification =>
76+
firstFactorVerification ?? secondFactorVerification;
77+
78+
/// Do we have a verification in operation>?
79+
bool get hasVerification => verification is Verification;
80+
7081
/// fromJson
7182
static SignIn fromJson(Map<String, dynamic> json) => _$SignInFromJson(json);
7283

@@ -84,17 +95,33 @@ class SignIn with InformativeToStringMixin {
8495
};
8596
}
8697

98+
/// Find the [Factor]s for this [SignIn] that match
99+
/// the [stage]
100+
///
101+
List<Factor> factorsFor(Stage stage) {
102+
return switch (stage) {
103+
Stage.first => supportedFirstFactors,
104+
Stage.second => supportedSecondFactors,
105+
};
106+
}
107+
108+
/// The factors for
109+
List<Factor> get factors => switch (status) {
110+
Status.needsFirstFactor => supportedFirstFactors,
111+
Status.needsSecondFactor => supportedSecondFactors,
112+
_ => const [],
113+
};
114+
115+
/// can we handle the password strategy?
116+
bool get canUsePassword => factors.any((f) => f.strategy.isPassword);
117+
87118
/// Find the [Factor] for this [SignIn] that matches
88119
/// the [strategy] and [stage]
89120
///
90121
/// Throw an error on failure
91122
///
92123
Factor factorFor(Strategy strategy, Stage stage) {
93-
final factors = switch (stage) {
94-
Stage.first => supportedFirstFactors,
95-
Stage.second => supportedSecondFactors,
96-
};
97-
for (final factor in factors) {
124+
for (final factor in factorsFor(stage)) {
98125
if (factor.strategy == strategy) return factor;
99126
}
100127
switch (stage) {

packages/clerk_auth/lib/src/models/client/sign_in.g.dart

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/clerk_auth/lib/src/models/client/strategy.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ class Strategy {
9292
/// saml strategy
9393
static const saml = Strategy(name: 'saml');
9494

95+
/// enterprise sso strategy
96+
static const enterpriseSSO = Strategy(name: 'enterprise_sso');
97+
9598
/// ticket strategy
9699
static const ticket = Strategy(name: 'ticket');
97100

@@ -121,6 +124,7 @@ class Strategy {
121124
ticket.name: ticket,
122125
web3MetamaskSignature.name: web3MetamaskSignature,
123126
web3CoinbaseSignature.name: web3CoinbaseSignature,
127+
enterpriseSSO.name: enterpriseSSO,
124128
};
125129

126130
// identification strategies
@@ -153,6 +157,9 @@ class Strategy {
153157
/// is known?
154158
bool get isKnown => isUnknown == false;
155159

160+
/// is password?
161+
bool get isPassword => this == password;
162+
156163
/// is some variety of oauth?
157164
bool get isOauth => name == _oauth || isOauthCustom || isOauthToken;
158165

@@ -199,9 +206,12 @@ class Strategy {
199206
/// required verification?
200207
bool get requiresVerification => requiresCode || requiresSignature;
201208

209+
/// is SSO?
210+
bool get isSSO => name == _oauth || this == enterpriseSSO;
211+
202212
/// requires redirect?
203213
bool get requiresRedirect =>
204-
name == _oauth || const [emailLink, saml].contains(this);
214+
name == _oauth || const [emailLink, enterpriseSSO].contains(this);
205215

206216
/// For a given [name] return the [Strategy] it identifies.
207217
/// Create one if necessary and possible

packages/clerk_auth/lib/src/models/status.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:clerk_auth/src/models/enums.dart';
12
import 'package:json_annotation/json_annotation.dart';
23
import 'package:meta/meta.dart';
34

@@ -93,9 +94,18 @@ class Status {
9394
/// is expired?
9495
bool get isExpired => this == expired;
9596

97+
/// is unknown?
98+
bool get isUnknown => this == unknown;
99+
96100
/// needs factor?
97101
bool get needsFactor => this == needsFirstFactor || this == needsSecondFactor;
98102

103+
/// Do we need factors for this stage?
104+
bool needsFactorFor(Stage stage) => switch (stage) {
105+
Stage.first => this == needsFirstFactor,
106+
Stage.second => this == needsSecondFactor,
107+
};
108+
99109
/// toString
100110
@override
101111
String toString() => name;

packages/clerk_flutter/l10n/en.arb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@
4444
"authenticatorApp": "authenticator app",
4545
"automaticInvitation": "Automatic invitation",
4646
"automaticSuggestion": "Automatic suggestion",
47+
"back": "Back",
4748
"backupCode": "backup code",
4849
"cancel": "Cancel",
4950
"cannotDeleteSelf": "You are not authorized to delete your user",
50-
"clickOnTheLinkThatSBeenSentToAndThenCheckBackHere": "Click on the link that‘s been sent to {identifier} and then check back here",
51-
"@clickOnTheLinkThatSBeenSentToAndThenCheckBackHere": {
51+
"clickOnTheLinkThatsBeenSentTo": "Click on the link that‘s been sent to {identifier} and then check back here",
52+
"@clickOnTheLinkThatsBeenSentTo": {
5253
"placeholders": {
5354
"identifier": {
5455
"type": "String"
@@ -228,6 +229,7 @@
228229
}
229230
}
230231
},
232+
"signInUsingEnterpriseSSO": "Sign in using Enterprise SSO",
231233
"signOut": "Sign out",
232234
"signOutIdentifier": "Sign out {identifier}",
233235
"@signOutIdentifier": {

packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ abstract class ClerkSdkLocalizations {
216216
/// **'Automatic suggestion'**
217217
String get automaticSuggestion;
218218

219+
/// No description provided for @back.
220+
///
221+
/// In en, this message translates to:
222+
/// **'Back'**
223+
String get back;
224+
219225
/// No description provided for @backupCode.
220226
///
221227
/// In en, this message translates to:
@@ -234,11 +240,11 @@ abstract class ClerkSdkLocalizations {
234240
/// **'You are not authorized to delete your user'**
235241
String get cannotDeleteSelf;
236242

237-
/// No description provided for @clickOnTheLinkThatSBeenSentToAndThenCheckBackHere.
243+
/// No description provided for @clickOnTheLinkThatsBeenSentTo.
238244
///
239245
/// In en, this message translates to:
240246
/// **'Click on the link that‘s been sent to {identifier} and then check back here'**
241-
String clickOnTheLinkThatSBeenSentToAndThenCheckBackHere(String identifier);
247+
String clickOnTheLinkThatsBeenSentTo(String identifier);
242248

243249
/// No description provided for @complete.
244250
///
@@ -732,6 +738,12 @@ abstract class ClerkSdkLocalizations {
732738
/// **'Sign in to {name}'**
733739
String signInTo(String name);
734740

741+
/// No description provided for @signInUsingEnterpriseSSO.
742+
///
743+
/// In en, this message translates to:
744+
/// **'Sign in using Enterprise SSO'**
745+
String get signInUsingEnterpriseSSO;
746+
735747
/// No description provided for @signOut.
736748
///
737749
/// In en, this message translates to:

0 commit comments

Comments
 (0)