Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign in/register with code in Flutter App #85

Merged
merged 8 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ class RegistrationBloc extends Bloc<RegistrationEvent, RegistrationState> {
try {
if (state.registrationFlow != null) {
emit(state.copyWith(isLoading: true, message: null));
await repository.updateRegistrationFlow(
final session = await repository.updateRegistrationFlow(
flowId: state.registrationFlow!.id,
group: event.group,
name: event.name,
value: event.value,
nodes: state.registrationFlow!.ui.nodes.toList());
authBloc.add(ChangeAuthStatus(status: AuthStatus.authenticated));
authBloc.add(AddSession(session: session));
}
} on BadRequestException<RegistrationFlow> catch (e) {
emit(state.copyWith(registrationFlow: e.flow, isLoading: false));
Expand Down
47 changes: 12 additions & 35 deletions flutter-ory-network/lib/pages/login.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ory_client/ory_client.dart';
Expand Down Expand Up @@ -52,47 +50,23 @@ class LoginForm extends StatelessWidget {
final nodes = state.loginFlow!.ui.nodes;

// get default nodes from all nodes
final defaultNodes = nodes.where((node) {
if (node.group == UiNodeGroupEnum.default_) {
if (node.attributes.oneOf.isType(UiNodeInputAttributes)) {
final attributes =
node.attributes.oneOf.value as UiNodeInputAttributes;
if (attributes.type == UiNodeInputAttributesTypeEnum.hidden) {
return false;
} else {
return true;
}
}
}
return false;
}).toList();
final defaultNodes = getNodesOfGroup(UiNodeGroupEnum.default_, nodes);

// get code nodes from all nodes
final codeNodes = getNodesOfGroup(UiNodeGroupEnum.code, nodes);

// get password nodes from all nodes
final passwordNodes =
nodes.where((node) => node.group == UiNodeGroupEnum.password).toList();
final passwordNodes = getNodesOfGroup(UiNodeGroupEnum.password, nodes);

// get lookup secret nodes from all nodes
final lookupSecretNodes = nodes
.where((node) => node.group == UiNodeGroupEnum.lookupSecret)
.toList();
final lookupSecretNodes =
getNodesOfGroup(UiNodeGroupEnum.lookupSecret, nodes);

// get totp nodes from all nodes
final totpNodes =
nodes.where((node) => node.group == UiNodeGroupEnum.totp).toList();
final totpNodes = getNodesOfGroup(UiNodeGroupEnum.totp, nodes);

// get oidc nodes from all nodes
final oidcNodes = nodes.where((node) {
if (node.group == UiNodeGroupEnum.oidc) {
if (node.attributes.oneOf.isType(UiNodeInputAttributes)) {
final attributes =
node.attributes.oneOf.value as UiNodeInputAttributes;
return Platform.isAndroid
? !attributes.value!.asString.contains('ios')
: attributes.value!.asString.contains('ios');
}
}
return false;
}).toList();
final oidcNodes = getNodesOfGroup(UiNodeGroupEnum.oidc, nodes);

return Stack(children: [
Padding(
Expand Down Expand Up @@ -137,6 +111,9 @@ class LoginForm extends StatelessWidget {
if (defaultNodes.isNotEmpty)
buildGroup<LoginBloc>(context, UiNodeGroupEnum.default_,
defaultNodes, _onInputChange, _onInputSubmit),
if (codeNodes.isNotEmpty)
buildGroup<LoginBloc>(context, UiNodeGroupEnum.code, codeNodes,
_onInputChange, _onInputSubmit),
if (passwordNodes.isNotEmpty)
buildGroup<LoginBloc>(context, UiNodeGroupEnum.password,
passwordNodes, _onInputChange, _onInputSubmit),
Expand Down
47 changes: 12 additions & 35 deletions flutter-ory-network/lib/pages/registration.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import 'dart:io' show Platform;

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ory_client/ory_client.dart';
Expand Down Expand Up @@ -58,47 +56,23 @@ class RegistrationFormState extends State<RegistrationForm> {
final nodes = state.registrationFlow!.ui.nodes;

// get default nodes from all nodes
final defaultNodes = nodes.where((node) {
if (node.group == UiNodeGroupEnum.default_) {
if (node.attributes.oneOf.isType(UiNodeInputAttributes)) {
final attributes =
node.attributes.oneOf.value as UiNodeInputAttributes;
if (attributes.type == UiNodeInputAttributesTypeEnum.hidden) {
return false;
} else {
return true;
}
}
}
return false;
}).toList();
final defaultNodes = getNodesOfGroup(UiNodeGroupEnum.default_, nodes);

// get code nodes from all nodes
final codeNodes = getNodesOfGroup(UiNodeGroupEnum.code, nodes);

// get password nodes from all nodes
final passwordNodes =
nodes.where((node) => node.group == UiNodeGroupEnum.password).toList();
final passwordNodes = getNodesOfGroup(UiNodeGroupEnum.password, nodes);

// get lookup secret nodes from all nodes
final lookupSecretNodes = nodes
.where((node) => node.group == UiNodeGroupEnum.lookupSecret)
.toList();
final lookupSecretNodes =
getNodesOfGroup(UiNodeGroupEnum.lookupSecret, nodes);

// get totp nodes from all nodes
final totpNodes =
nodes.where((node) => node.group == UiNodeGroupEnum.totp).toList();
final totpNodes = getNodesOfGroup(UiNodeGroupEnum.totp, nodes);

// get oidc nodes from all nodes
final oidcNodes = nodes.where((node) {
if (node.group == UiNodeGroupEnum.oidc) {
if (node.attributes.oneOf.isType(UiNodeInputAttributes)) {
final attributes =
node.attributes.oneOf.value as UiNodeInputAttributes;
return Platform.isAndroid
? !attributes.value!.asString.contains('ios')
: attributes.value!.asString.contains('ios');
}
}
return false;
}).toList();
final oidcNodes = getNodesOfGroup(UiNodeGroupEnum.oidc, nodes);

return Stack(children: [
Padding(
Expand Down Expand Up @@ -131,6 +105,9 @@ class RegistrationFormState extends State<RegistrationForm> {
if (defaultNodes.isNotEmpty)
buildGroup<RegistrationBloc>(context, UiNodeGroupEnum.default_,
defaultNodes, _onInputChange, _onInputSubmit),
if (codeNodes.isNotEmpty)
buildGroup<RegistrationBloc>(context, UiNodeGroupEnum.code,
codeNodes, _onInputChange, _onInputSubmit),
if (passwordNodes.isNotEmpty)
buildGroup<RegistrationBloc>(context, UiNodeGroupEnum.password,
passwordNodes, _onInputChange, _onInputSubmit),
Expand Down
62 changes: 36 additions & 26 deletions flutter-ory-network/lib/repositories/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:one_of/one_of.dart';
import 'package:ory_client/ory_client.dart';
import 'package:collection/collection.dart';
import 'package:ory_network_flutter/services/exceptions.dart';
import 'package:ory_network_flutter/widgets/helpers.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

import '../services/auth.dart';
Expand Down Expand Up @@ -89,7 +90,7 @@ class AuthRepository {
}
}

Future<Session?> updateRegistrationFlow(
Future<Session> updateRegistrationFlow(
{required String flowId,
required UiNodeGroupEnum group,
required String name,
Expand Down Expand Up @@ -214,41 +215,51 @@ class AuthRepository {
required String name,
required String value,
required List<UiNode> nodes}) {
// find default nodes (e.g identifier)
var inputNodes =
nodes.where((node) => node.group == UiNodeGroupEnum.default_).toList();
// create maps from attribute names and their values
var nestedMaps = inputNodes.map((node) {
final attributes = asInputAttributes(node);
final nodeValue = getInputNodeValue(attributes);

return generateNestedMap(attributes.name, nodeValue);
}).toList();

// if name of submitted node is method, find all nodes that belong to the group
if (name == 'method') {
// get input nodes of the same group
final inputNodes = nodes.where((p0) {
if (p0.attributes.oneOf.isType(UiNodeInputAttributes)) {
final attributes = p0.attributes.oneOf.value as UiNodeInputAttributes;
// if group is password, find identifier
if (group == UiNodeGroupEnum.password &&
p0.group == UiNodeGroupEnum.default_ &&
attributes.name == 'identifier') {
return true;
}
return p0.group == group &&
final methodNodes = nodes.where((node) {
if (isInputNode(node)) {
final attributes = asInputAttributes(node);

return node.group == group &&
attributes.type != UiNodeInputAttributesTypeEnum.button &&
attributes.type != UiNodeInputAttributesTypeEnum.submit;
} else {
return false;
}
});
}).toList();
inputNodes = inputNodes + methodNodes;
// create maps from attribute names and their values
final nestedMaps = inputNodes.map((e) {
final attributes = e.attributes.oneOf.value as UiNodeInputAttributes;
final methodMaps = methodNodes.map((node) {
final attributes = asInputAttributes(node);
final nodeValue = getInputNodeValue(attributes);

return generateNestedMap(
attributes.name, attributes.value?.asString ?? '');
return generateNestedMap(attributes.name, nodeValue);
}).toList();

// merge nested maps into one
final mergedMap =
nestedMaps.reduce((value, element) => value.deepMerge(element));

return mergedMap;
// add method maps to default maps
nestedMaps.addAll(methodMaps);
} else {
return {name: value};
// add single map to default maps
nestedMaps.add({name: value});
}

// merge nested maps into one
final mergedMap =
nestedMaps.reduce((value, element) => value.deepMerge(element));

return mergedMap;
}

RegistrationFlow changeRegistrationNodeValue(
Expand Down Expand Up @@ -283,9 +294,8 @@ class AuthRepository {
required String value}) {
// get edited node
final node = nodes.firstWhereOrNull((element) {
if (element.attributes.oneOf.isType(UiNodeInputAttributes)) {
return (element.attributes.oneOf.value as UiNodeInputAttributes).name ==
name;
if (isInputNode(element)) {
return asInputAttributes(element).name == name;
} else {
return false;
}
Expand Down
18 changes: 17 additions & 1 deletion flutter-ory-network/lib/services/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class AuthService {
}
}

/// Create login flow
/// Create login flow with [aal]
Future<LoginFlow> createLoginFlow({required String aal}) async {
try {
final token = await storage.getToken();
Expand Down Expand Up @@ -114,6 +114,14 @@ class AuthService {
..provider = value['provider']
..idToken = value['id_token']
..idTokenNonce = value['nonce']));
case UiNodeGroupEnum.code:
oneOf = OneOf.fromValue1(
value: UpdateLoginFlowWithCodeMethod((b) => b
..csrfToken = ''
..method = group.name
..identifier = value['identifier']
..code = value['resend'] != null ? null : value['code']
..resend = value['resend']));

// if method is not implemented, throw exception
default:
Expand Down Expand Up @@ -242,6 +250,14 @@ class AuthService {
..provider = value['provider']
..idToken = value['id_token']
..idTokenNonce = value['nonce']));
case UiNodeGroupEnum.code:
oneOf = OneOf.fromValue1(
value: UpdateRegistrationFlowWithCodeMethod((b) => b
..csrfToken = ''
..method = group.name
..traits = JsonObject(value['traits'])
..code = value['resend'] != null ? null : value['code']
..resend = value['resend']));

// if method is not implemented, throw exception
default:
Expand Down
Loading
Loading