用Auth0进行Flutter认证和授权,第四部分:角色和权限

318 阅读9分钟

在上一节中,你学会了如何将实时聊天添加到应用程序中,并设置MJ Coffee应用程序以加载不同的聊天屏幕。

在本节中,你将学习如何在Auth0和Flutter应用程序中管理角色和权限,以及如何应用适当的授权流程。您还将学习如何在Flutter应用程序中利用RBAC和基于权限的功能。

如果您想在关注构建和执行步骤的同时略过这些内容,请寻找🛠表情符号。

管理角色

在本教程的前一部分中,你学到了如何在Auth0仪表板中创建角色。现在是时候看看你为什么需要角色,并学习如何在你的应用程序中利用它们。

基于角色的访问控制(RBAC)是一种根据用户的角色来分配权限的方法。它提供了一种简单的访问管理方法,比单独给用户分配权限更不容易出错。

例如,假设你使用RBAC来控制MJ咖啡应用程序中的客户/员工访问。在这种情况下,你可以给员工一个角色,允许他们更新用户的详细资料或访问社区聊天屏幕。相比之下,客户将查看支持屏幕,但不会被授权执行任务,如删除消息或上传附件。

当规划你的访问控制策略时,一般来说,给用户分配少量的权限,使他们能够完成他们的工作是一个好主意。

你可以利用Auth0 Actions,在每个用户注册后自动为他们分配角色。

🛠 再一次,创建一个新的自定义动作。在Auth0仪表板的左栏菜单中,选择行动,然后是流程。会出现 "选择流程"页面。选择发布用户注册

Creating a new post user registration

🛠将动作命名为Assign Role ,并添加auth0 npm模块,版本为 2.35.1.

🛠 添加以下处理程序,它将在执行PostUserRegistration 流程时被调用。

// Auth0 Actions

const ManagementClient = require('auth0').ManagementClient;
const AuthenticationClient = require('auth0').AuthenticationClient;

exports.onExecutePostUserRegistration = async (event) => {
  const DOMAIN = '{YOUR_DOMAIN}';
  const auth0 = new AuthenticationClient({
    domain: DOMAIN,
    clientId: event.secrets.M2M_CLIENT_ID,
    clientSecret: event.secrets.M2M_CLIENT_SECRET,
  });
  const response = await auth0.clientCredentialsGrant({
    audience: `https://${DOMAIN}/api/v2/`,
    scope: 'read:users update:users',
  });
  const API_TOKEN = response.access_token;
  const management = new ManagementClient({
    domain: DOMAIN,
    token: API_TOKEN,
  });

  if (event.user.email.endsWith('@mjcoffee.app')) {
    // employee
    await management.assignRolestoUser(
      { id: event.user.user_id },
      { roles: ['rol_CHpJMdZUPCLzo6E2'] }
    );
  } else {
    // customer
    await management.assignRolestoUser(
      { id: event.user.user_id },
      { roles: ['rol_fG50GuNE9S72jNZn'] }
    );
  }
};

让我们一步一步地分析这个处理程序的逻辑。

该处理程序从auth0 模块中导入ManagementClientAuthenticationClient ,这是一个Node.js SDK,方便地提供官方的Auth0公共API。

然后,它定义了你的域名。 DOMAIN.

🛠 在处理程序中,将 {YOUR_DOMAIN}替换为你的应用程序的域名,你可以在Auth0仪表板上找到。

处理程序初始化了一个认证客户端,将应用程序的域、客户端ID和客户端秘密传递给其构造函数。

**请注意,应用程序的客户端在这里有点特别!**这不是你到目前为止所创建和使用的应用程序。你需要创建一个机器对机器(M2M)应用程序或Auth0非交互式客户端,这使得请求客户端证书授予成为可能。

🛠 在浏览器的新标签中打开Auth0仪表板。转到应用程序页面,创建一个新的应用程序,选择机器对机器的应用程序类型。

Creating a new M2M application in the Auth0 dashboard

🛠 一旦M2M应用被创建,记下客户ID和客户秘密。你将需要在下一步将它们添加到你的Actions的秘密中,然后去API标签,授权Auth0管理API。

如前所述,限制权限总是一个好主意。因此,只选择那些需要用这个客户端执行动作的权限。

🛠 我们希望能够为用户添加角色,所以选择 read:usersupdate:users权限。

Authorizing the API

🛠 在行动中添加以下内容。

  • 密钥 M2M_CLIENT_ID,以客户的ID作为其对应的值
  • 密钥 M2M_CLIENT_SECRET,以客户的秘密为值

现在你可以通过指定受众和作用域来成功地验证一个Auth0客户端并请求一个管理API令牌。作用域是你在前面的步骤中在Auth0管理API下的权限标签中选择的那些。

一旦访问权被授予并收到令牌,你就可以创建一个管理客户端。

这个MJ Coffee应用程序的逻辑相对简单。

if (event.user.email.endsWith('@mjcoffee.app')) {
  // employee
} else {
  // customer
}

简单地说,如果用户的电子邮件地址的结尾是 @mjcoffee.app他们将被分配为雇员角色;否则,他们将被分配为客户角色。当然,这可能会根据你的实现而改变。

最后,处理程序调用 assignRolesToUser()management 客户端上并传递。

  • 一个包含用户ID的地图,和
  • 一个角色ID的列表。

要获得一个角色的ID,在Auth0仪表板的左栏菜单中选择用户管理,然后选择角色

Getting a Role’s ID

太棒了不要忘记部署你的功能。

🛠 最后一步,进入流程,然后是发布用户注册。将你的Assign Role 自定义动作添加到流程中。

如果你迫不及待地想测试你到目前为止所创建的东西,请导航到Auth0仪表板的用户页面,并创建新的用户,包括你在函数逻辑中指定的有和没有电子邮件地址的用户。然后,进入角色选项卡--你应该看到,每个用户的角色都是自动分配的。

Users and their automatically assigned roles

管理权限

你经常需要创建一个自定义的API来定义权限。然后你可以把这些权限分配给角色,然后再分配给用户。

🛠 在Auth0仪表板上,在左栏菜单中选择应用程序,在子菜单中选择API,然后点击创建API按钮,创建一个新的自定义API。

🛠给API一个可识别的名字,比如说 StreamChat Management API,然后定义你的标识符。这个标识符将成为你的API受众。注意,标识符不能被修改。命名标识符的一个好的做法是使用一个URL,即使这个URL是不公开的。例如,你可以将标识符命名为 https://getStreamChat.mjcoffee.app/v1/.

Creating a new custom API

🛠 一旦创建了API,进入RBAC设置部分,启用RBAC并在访问标记中添加权限。该API应该被标记为第一方,以便你可以打开允许跳过用户同意。打开允许离线访问,这样Auth0将允许应用程序为这个API询问刷新令牌。

RBAC API

🛠导航到权限选项卡,添加你的权限和描述。我建议至少要添加这些权限。

  • delete.user.message
  • edit.user.message
  • upload.attachments

Custome permissions

你定义的权限越多,越明确,你就越能控制资源。

🛠 接下来,前往用户管理下的角色。进入每个角色,分配以下权限。

  • 雇员
    • edit.user.message
    • upload.attachments
  • 客户
    • edit.user.message
  • 管理员
    • delete.user.message
    • edit.user.message
    • upload.attachments

Defining custom permissions

你已经设法创建了角色,用户在注册时被自动分配角色。虽然现在角色分配是自动的,但你仍然可以手动改变用户角色。例如,你可以在Auth0仪表板上为特定的用户添加管理员角色。

剩下的唯一步骤是将角色和权限暴露给Flutter应用程序可以消费的idTokenaccessToken

暴露角色和权限

🛠这一步与你在分配角色时所做的相当相似。作为提醒,以下是步骤。

  • 创建一个自定义动作,触发登录/后登录
  • 将该动作命名为 "揭示用户角色和权限"
  • 将机器对机器应用程序的客户ID和客户密钥添加到该动作的秘密中
  • 添加auth0 npm包的版本 2.35.1

🛠完成后,按照下面的代码操作。

// Auth0 Actions

const ManagementClient = require('auth0').ManagementClient;
const AuthenticationClient = require('auth0').AuthenticationClient;
exports.onExecutePostLogin = async (event, api) => {
  const DOMAIN = 'mhadaily.eu.auth0.com';
  const auth0 = new AuthenticationClient({
    domain: DOMAIN,
    clientId: event.secrets.M2M_CLIENT_ID,
    clientSecret: event.secrets.M2M_CLIENT_SECRET,
  });
  const response = await auth0.clientCredentialsGrant({
    audience: `https://${DOMAIN}/api/v2/`,
    scope: 'read:users update:users read:roles',
  });
  const API_TOKEN = response.access_token;
  const management = new ManagementClient({
    domain: DOMAIN,
    token: API_TOKEN,
  });

  const params = { id: event.user.user_id };
  const roles = await management.getUserRoles(params);
  const permissions = await management.getUserPermissions(params);

  const namespace = 'https://users.mjcoffee.app';
  if (event.authorization) {
    api.idToken.setCustomClaim(`${namespace}/roles`, roles);
    api.accessToken.setCustomClaim(`${namespace}/roles`, roles);
    api.idToken.setCustomClaim(`${namespace}/permissions`, permissions);
    api.accessToken.setCustomClaim(`${namespace}/permissions`, permissions);
  }
};

这段代码与你以前写过一次的代码相当相似。第一个变化是,它把 read:roles到范围中。请确保你已经在M2M应用程序下启用了这个权限,就像你曾经为 read:users update:users权限;否则,你将面临一个 "未授权 "的错误。

然后它调用 getUserRoles()getUserPermissions(),传递用户ID。这些函数将分别返回用户角色和权限。

在定义了一个命名空间后,代码会调用 setCustomClaim()来为ID和访问令牌添加角色和权限的自定义请求。

🛠 确保你部署,然后导航到流量。你要把新的自定义动作添加到登录流程中,就在你之前创建的动作之前。

new flow login

到目前为止还不错,但你仍然需要到Flutter应用程序中,为模型添加角色和权限。

阅读 Flutter 中的角色和权限

现在,角色和权限都可以在ID和访问令牌中找到,你可以把它们分别添加到Auth0IdTokenAuth0UserInfo

🛠 首先,创建一个名为 auth0_roles.dart的文件,该文件位于 /lib/models文件夹中。

// /lib/models/auth0_roles.dart

import 'package:json_annotation/json_annotation.dart';

part 'auth0_roles.g.dart';

enum Role {
  Employee,
  Admin,
  Customer,
}

@JsonSerializable()
class Auth0Role {
  Auth0Role({
    required this.id,
    required this.name,
    required this.description,
  });

  final String id;
  final Role name;
  final String description;

  factory Auth0Role.fromJson(Map<String, dynamic> json) =>
      _$Auth0RoleFromJson(json);

  Map<String, dynamic> toJson() => _$Auth0RoleToJson(this);

  @override
  String toString() {
    return '''$name''';
  }
}

🛠 然后在同一目录下创建 auth0_permissions.dart在同一目录下,用下面的代码创建

// /lib/models/auth0_permissions.dart

import 'package:json_annotation/json_annotation.dart';

part 'auth0_permissions.g.dart';

class UserPermissions {
  static const String delete = 'delete.user.message';
  static const String edit = 'edit.user.message';
  static const String upload = 'upload.attachments';
}

@JsonSerializable()
class Auth0Permission {
  Auth0Permission({
    required this.permissionName,
    required this.description,
    required this.resourceServerName,
    required this.resourceServerIdentifier,
    required this.sources,
  });

  @JsonKey(name: 'permission_name')
  final String permissionName;

  final String description;
  @JsonKey(name: 'resource_server_name')
  final String resourceServerName;
  @JsonKey(name: 'resource_server_identifier')
  final String resourceServerIdentifier;

  final List<Auth0PermissionsSource> sources;

  factory Auth0Permission.fromJson(Map<String, dynamic> json) =>
      _$Auth0PermissionFromJson(json);

  Map<String, dynamic> toJson() => _$Auth0PermissionToJson(this);

  @override
  String toString() {
    return '''$permissionName''';
  }
}

@JsonSerializable()
class Auth0PermissionsSource {
  Auth0PermissionsSource({
    required this.sourceId,
    required this.sourceName,
    required this.sourceType,
  });

  @JsonKey(name: 'source_id')
  final String sourceId;
  @JsonKey(name: 'source_name')
  final String sourceName;
  @JsonKey(name: 'source_type')
  final String sourceType;

  factory Auth0PermissionsSource.fromJson(Map<String, dynamic> json) =>
      _$Auth0PermissionsSourceFromJson(json);

  Map<String, dynamic> toJson() => _$Auth0PermissionsSourceToJson(this);

  @override
  String toString() {
    return '''
      sourceId: $sourceId,
      sourceName: $sourceName,
      sourceType: $sourceType,
      ''';
  }
}

你想在这里实现的是对每个模型的角色和权限进行序列化和反序列化。

🛠 下一步是更新Auth0IdToken 模型。

// /lib/models/auth0_id_token.dart

@JsonSerializable()
class Auth0IdToken {
  Auth0IdToken({
  
...

    required this.roles,
    required this.permissions,

...

}}

...

  @JsonKey(name: 'https://users.mjcoffee.app/roles')
  final List<Auth0Role> roles;

  @JsonKey(name: 'https://users.mjcoffee.app/permissions')
  final List<Auth0Permission> permissions;
  
...

}

🛠 对Auth0User 模型做同样的事情。

// /lib/models/auth0_user.dart

@JsonSerializable()
class Auth0User {
  Auth0User({
  
...

    required this.permissions,
    required this.roles,
    
...

})

...

  @JsonKey(name: 'https://users.mjcoffee.app/roles')
  final List<Auth0Role> roles;

  @JsonKey(name: 'https://users.mjcoffee.app/permissions')
  final List<Auth0Permission> permissions;
  
...

}

🛠 最后,运行build_runner 命令以确保模型正确生成。

flutter pub run build_runner build --delete-conflicting-outputs

现在你可以重新启动MJ Coffee应用程序,一切都应该按预期进行。

Flutter中基于角色的屏幕

现在终于到了根据用户的角色设置加载屏幕的时候了。如果你还记得,我们想为客户和员工分别添加支持和社区屏幕。你在前面创建了这两个屏幕。

Auth0User 中定义getter方法来确定用户是否有特定的角色或权限会更容易。

🛠 在Auth0User 类中添加代码,如下所示。

// /lib/models/auth0_user.dart

class Auth0User {

...

  bool get hasImage => picture.isNotEmpty;
  bool can(String permission) => permissions
      .where(
        (p) => p.permissionName == permission,
      )
      .isNotEmpty;

  get isAdmin => roles.where((role) => role.name == Role.Admin).isNotEmpty;
  get isEmployee =>
      roles.where((role) => role.name == Role.Employee).isNotEmpty;
  get isCustomer =>
      roles.where((role) => role.name == Role.Customer).isNotEmpty;
      
...

}

这些getter是不言自明的。

🛠 接下来,打开 /lib/screens/menu.dart并找到_MenuScreenState 类中的tabs 列表。

// /lib/screens/menu.dart

  ...
  
  final List<Widget> tabs = [
      MenuList(coffees: coffees),
      if (AuthService.instance.profile?.isCustomer)
        SupportChatScreen()
      else
        CommunityScreen(),
      ProfileScreen(),
    ];
    
  ...

🛠 在同一个文件中,找到BottomNavigationBar ,将BottomNavigationBarItem 添加到列表的第二个位置。

// /lib/screens/menu.dart

...

BottomNavigationBar _bottomNavigationBar(Auth0User? user) {
    return BottomNavigationBar(
      
      ...
      
      items: <BottomNavigationBarItem>[
      
        ...
      
        BottomNavigationBarItem(
          icon: AuthService.instance.profile?.isCustomer
              ? Icon(Icons.support_agent)
              : Icon(Icons.group),
          label: AuthService.instance.profile?.isCustomer
              ? "Support"
              : "Community",
        ),
        
       ...
       
      ],
      
     ...
     
    );
  }
  
...

🛠 为了使用户界面看起来更好,你可以把用户的头像添加到appBar ,这样,完整的实现就如下所示。

// /lib/screens/menu.dart

class MenuScreen extends StatefulWidget {
  static String routeName = 'menuScreen';
  static Route<MenuScreen> route() {
    return MaterialPageRoute<MenuScreen>(
      settings: RouteSettings(name: routeName),
      builder: (BuildContext context) => MenuScreen(),
    );
  }

  @override
  _MenuScreenState createState() => _MenuScreenState();
}

class _MenuScreenState extends State<MenuScreen> {
  int _selectedIndex = 0;
  Auth0User? profile = AuthService.instance.profile;

  @override
  void initState() {
    super.initState();
  }

  final List<Widget> tabs = [    MenuList(coffees: coffees),    if (AuthService.instance.profile?.isCustomer)      SupportChatScreen()    else      CommunityScreen(),    ProfileScreen(),  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        automaticallyImplyLeading: false,
        centerTitle: false,
        title: Text("Welcome ${profile?.name}"),
        actions: [
          _avatar(profile),
        ],
      ),
      body: tabs[_selectedIndex],
      bottomNavigationBar: _bottomNavigationBar(profile),
    );
  }

  BottomNavigationBar _bottomNavigationBar(Auth0User? user) {
    return BottomNavigationBar(
      backgroundColor: Colors.white,
      type: BottomNavigationBarType.fixed,
      unselectedItemColor: Colors.brown.shade300,
      items: <BottomNavigationBarItem>[
        BottomNavigationBarItem(
          icon: Icon(Icons.list_alt),
          label: "Menu",
        ),
        BottomNavigationBarItem(
          icon:
              user?.isCustomer ? Icon(Icons.support_agent) : Icon(Icons.group),
          label: user?.isCustomer ? "Support" : "Community",
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.person),
          label: "Profile",
        ),
      ],
      currentIndex: _selectedIndex,
      selectedItemColor: Colors.brown.shade800,
      onTap: _onItemTapped,
    );
  }

  Padding _avatar(Auth0User? profile) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: FittedBox(
        fit: BoxFit.cover,
        child: ClipRRect(
          clipBehavior: Clip.antiAlias,
          borderRadius: BorderRadius.all(Radius.circular(600)),
          child: Container(
            child: _avatarPhoto(profile),
          ),
        ),
      ),
    );
  }

  Widget _avatarPhoto(Auth0User? profile) {
    return profile != null && profile.hasImage
        ? Image.network(
            profile.picture,
            width: 20,
            height: 20,
          )
        : Container(
            width: 20,
            height: 20,
            color: darkBrown,
            child: Center(
              child: Text('${profile?.name[0].toUpperCase()}'),
            ),
          );
  }
}

你可以创建一个新的用户,并为其分配 "雇员 "角色,这样你也可以测试雇员角色屏幕。

做得很好!重新启动你的应用程序,注销,并再次登录,你将看到你的角色的相应屏幕。

 role base screen flutter

但这还不是全部!你仍然需要一个雇员ID,以便在当前登录的客户和雇员之间创建一个私人通道。

你可以定义一个可以返回可用代理的API来创建一个通道。然而,另一个适用于MJ咖啡应用的策略是通过ID tokens的自定义索赔来检索所有员工的用户ID,并随机挑选其中的一个。

你可以创建其他的自定义动作,类似于前面关于权限和角色的步骤。因为你已经做了两次,所以我就不陪你走所有的步骤了。

🛠 将此动作命名为Retrieve Employees User IDs ,并定义其逻辑如下。

// Auth0 Action

const ManagementClient = require('auth0').ManagementClient;
const AuthenticationClient = require('auth0').AuthenticationClient;
exports.onExecutePostLogin = async (event, api) => {
  const DOMAIN = 'mhadaily.eu.auth0.com';
  const auth0 = new AuthenticationClient({
    domain: DOMAIN,
    clientId: event.secrets.M2M_CLIENT_ID,
    clientSecret: event.secrets.M2M_CLIENT_SECRET,
  });
  const response = await auth0.clientCredentialsGrant({
    audience: `https://${DOMAIN}/api/v2/`,
    scope: 'read:users read:roles',
  });
  const API_TOKEN = response.access_token;
  const management = new ManagementClient({
    domain: DOMAIN,
    token: API_TOKEN,
  });
  const params = { id: event.secrets.EMPLOYEE_ROLE_ID, per_page: 10, page: 0 };
  const employees = await management.getUsersInRole(params);
  const employee_ids = employees.map((employee) => employee.user_id);
  const namespace = 'https://employees.mjcoffee.app';
  if (event.authorization) {
    api.idToken.setCustomClaim(`${namespace}/id`, employee_ids);
  }
};

同样,你将使用ManagementClient ,根据角色获得前十个用户,调用 getUsersInRole()并传递角色ID,即 EMPLOYEE_ROLE_ID从secrets中识别出来,然后定义命名空间,并在idToken 上设置一个自定义请求。

🛠 最后,部署这个动作,将其添加到Login流程中,紧接着GetStream User Token动作,并应用。

 role in login flow

在Flutter应用程序中找到Auth0IdTokenAuth0User 类,并为这两个类添加一个新的属性:availableAgents

🛠在Auth0IdToken ,你应该有。

// /lib/models/auth0_id_token.dart

@JsonSerializable()
class Auth0IdToken {
  Auth0IdToken({
  
  ...
  
    required this.availableAgents,
    
    ...
    
  });
  
...

  @JsonKey(name: 'https://employees.mjcoffee.app/id', defaultValue: [])
  final List<String> availableAgents;
  
...

}

🛠 ......而在Auth0User ,你可以做同样的事情。

// /lib/models/auth0_user.dart

@JsonSerializable()
class Auth0User {
  Auth0User({
  
  ...
  
    required this.availableAgents,
  
    ...
    
  });
  
...

  @JsonKey(name: 'https://employees.mjcoffee.app/id', defaultValue: [])
  final List<String> availableAgents;

...

}

🛠 不要忘记运行这个。

flutter pub run build_runner build --delete-conflicting-outputs

🛠 如果你决定只对用户类做这样的修改,那是完全可以的。在ChatService 类中找到createSupportChat 。你把雇员的ID留空了,所以现在你可以重构这个,随机挑选一个雇员ID。

// /lib/services/chat_service.dart

 String? _currentEmployeeId;

Future<Channel> createSupportChat(List<String> availableAgents) async {
    // skip if the chat is still open with current employeeId
    if (_currentEmployeeId == null) {
      final _random = new Random();
      final randomNumber = 0 + _random.nextInt(availableAgents.length - 0);
      final String employeeId = availableAgents[randomNumber].split('|').join('');
      _currentEmployeeId = employeeId;
    }

    final channel = client.channel(
      'support',
      id: _currentChannelId,
      extraData: {
        'name': 'MJCoffee Support',
        'members': [
          _currentEmployeeId,
          client.state.user!.id,
        ]
      },
    );
    await channel.watch();
    _currentChannelId = channel.id;
    return channel;
  }

让我们研究一下这段代码。首先,它向createSupportChat 传递了一个雇员ID的列表。然后,它确保它存储的是当前有一个开放支持聊天的雇员ID,以避免重新创建一个新通道。

最后,它从列表中随机选取一个ID,并与当前客户创建一个频道。

这个解决方案可能不是最好的方案。然而,对于我们的小咖啡店来说,它是可行的。理想情况下,你会定义一个API,可以按需向客户返回一个可用的员工。我可能会再写一篇文章,向你展示你如何能更好地使用其他解决方案。

🛠最后,找到 createChannel()``_SupportChatScreenState 的方法(它在 /lib/screens/support.dart文件),并重构它以传递availableAgents

// /lib/screens/support.dart

...

createChannel() async {
    if (profile != null) {
      final _channel = await ChatService.instance.createSupportChat(
        profile!.availableAgents,
      );
      setState(() {
        channel = _channel;
      });
    }
  }
  
  ...

你已经在Stream chat中注册了所有员工的ID,这一点非常重要。通常情况下,用户可以按照员工应该登录的方式登录,一切都会正常。但是,如果您仍然没有注册所有的员工,您可能会得到一个错误,信息是 The following users are specified in channel.members but don't exist.如果您在登录流程和自定义令牌生成动作创建之前就已经创建了任何用户,通常会发生这种情况。

做得很好!你可以重新启动你的应用程序,这次你可以看到支持频道的屏幕。

基于权限的功能

在应用角色在应用程序中拥有特定类型的访问之后,你可以更深入一步,根据从角色继承的用户权限使用功能。

你已经在上一节中定义了can 方法,Auth0User 。这个方法的目的是检查用户是否有权限。让我们来使用它。

🛠 在文件中找到MessageInput ,你可以用 替换它。 support.dart文件中,你可以将其替换为

// /lib/screens/support.dart

...

MessageInput(
  disableAttachments: !profile!.can(UserPermissions.upload),
  sendButtonLocation: SendButtonLocation.inside,
  actionsLocation: ActionsLocation.leftInside,
  showCommandsButton: !profile?.isCustomer,
),

...

在上面的实现中,disableAttachments 是基于用户的权限而启用的,或者说showCommandsButton 只对客户角色有效。

你可以采取的另一种方法是限制删除消息的功能,并应用 UserPermissions.delete来删除适用的用户界面。

此外,你可能想在你的后端或API上申请这些权限来执行。我将把这部分作为家庭作业留下。

关闭支持聊天频道

在本教程的最后一节,我想告诉你如何关闭一个支持频道的聊天。

🛠首先,你需要为ChatService 类创建一个方法来发送关闭频道的命令。

// /lib/services/chat_service.dart

...

Future<void> archiveSupportChat() async {
   await client.hideChannel(
      _currentChannelId!,
      'support',
      clearHistory: true,
    );
  client.channel('support', id: _currentChannelId).dispose();
  _currentChannelId = null;
  _currentEmployeeId = null;
}

...

在这个实现中,你可以隐藏一个现有ID和类型为support 的聊天,最后,将_currentChannelId_currentEmployeeId 都设置为 null这样,下次用户来到支持界面时,他们会看到一个新的频道被创建,并且会将他们连接到另一个员工。

隐藏一个通道使它对查询通道不可见。如果用户向它添加新的消息或调用 show()方法来消除隐藏状态。

然而,还有其他的可能性。例如,你可以archivedelete一个频道。目前,archive 没有暴露在Stream Dart SDK中。因此,就目前而言,你可以隐藏一个频道。

🛠 接下来,在文件中找到MessageInput ,并添加 。 support.dart文件,并添加actions

// /lib/screens/support.dart
{

...

MessageInput(
  actions: [_closeChat()],
  disableAttachments: !profile!.can(UserPermissions.upload),
  sendButtonLocation: SendButtonLocation.inside,
  actionsLocation: ActionsLocation.leftInside,
  showCommandsButton: !profile?.isCustomer,
),

...

  /// method in the class
  CommonButton _closeChat() {
    return CommonButton(
      onPressed: () {
        ChatService.instance.archiveSupportChat();
        CoffeeRouter.instance.push(MenuScreen.route());
      },
      child: Icon(
        Icons.close,
        color: Colors.white,
      ),
    );
  }
}

actions 参数为GetStream 聊天输入用户界面添加了一个额外的动作列表。你可以调用archiveSupportChat 方法 OnPressed()并隐藏聊天,并将用户重定向到菜单屏幕,显示讨论已关闭的适当信息。他们可以通过返回支持界面重新打开。

结论

认证和授权是大多数应用程序的复杂但必要的功能,它们的实现和管理可能很棘手。Auth0提供了一个可靠的服务,承担了这些任务。你可以在Flutter应用程序中使用这一服务,而无需建立服务器或维护基础设施。你还可以利用无服务器工具,如Auth0 Actions,你可以用它来增加认证和授权过程的复杂性。

你已经看到了如何通过在Flutter应用程序中使用GetStreamChat 添加支持聊天来加快你的开发速度。您还看到了如何通过利用从Auth0通过令牌收到的角色和权限来限制用户可用的功能。

恭喜您!您已经走过了漫长的道路。在本教程的四个部分中,你已经走了很长的路,我希望你已经学到了很多东西。这仅仅是个开始--你仍然可以使用Stream和Auth0实现和配置很多东西,并将你的应用程序提升到新的水平。