如何解决为Swift / Firestore应用创建唯一的用户名
我已经通过Firebase文档,Swift / Xcode文档以及此处的Stack Overflow做了大量研究。不幸的是,我无法找到解决该问题的连贯且可行的解决方案-并且似乎已经以许多不同的方式尝试过多次该问题。
我不是专业编码员。不用说,我正在弄清楚这一点,并在将其作为编码项目来帮助他们学习的同时教我的孩子。
最终目标:允许swift / xcode应用程序创建用户,但前提是该用户的userName尚未被使用。
相关堆栈溢出讨论:
- Cloud Firestore: Enforcing Unique User Names
- Firestore Security Rule to Check if Character Username Already Exists
- Firestore Unique Index or Unique Constraint
我将附加当前的Swift代码,并在此问题上取得进展时对其进行更新。当前代码是用于Xcode视图控制器的注册过程。这段代码在Firestore数据库中创建了两个不同的集合:“用户”(包含用户的个人资料信息)和“用户名”(实质上是所有用户名的索引)。
此索引(集合:用户名)有一个以每个用户名命名的文档。这个想法是创建一个Firebase规则,不允许用户创建一个用户名=(集合中文档名称:用户名)的帐户。
更新
似乎有两种方法可以实现最终目标。一个是快速代码中的有机组成部分,但这可能会带来安全问题,因为该代码可能会被邪恶的参与者绕开。另一种方法是通过firebase数据库中的规则,但是我做起来并不那么流利(我将做更多的研究并向您报告)。也许最好的做法就是同时做这两个。
这是当前代码。
// SignUpViewController.swift
// copyright © 2020 by Mix. All rights reserved.
// Version 1.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class SignUpViewController: UIViewController,UITextFieldDelegate,UIPickerViewDelegate,UIPickerViewDataSource {
@IBOutlet weak var signUpButton: UIButton!
@IBOutlet weak var errorLabel: UILabel!
@IBOutlet weak var userNameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var verifyPasstextField: UITextField!
@IBOutlet weak var firstNameTextField: UITextField!
@IBOutlet weak var lastNameTextField: UITextField!
@IBOutlet weak var stateTextField: UITextField!
@IBOutlet weak var birthdayTextField: UITextField!
let datePicker = UIDatePicker()
let id = Auth.auth().currentUser!.uid
let email = Auth.auth().currentUser!.email
let state_arr = ["","AL","AK","AZ","AR","CA","CO","CT","DE","FL","GA","HI","ID","IL","IN","IA","KS","KY","LA","ME","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VT","VA","WA","WV","WI","WY"]
//picker view
let statePickerView = UIPickerView()
//Hold Current arr
var currentArr : [String] = []
//that hold current text field
var activeTextField : UITextField?
func createDatePicker() {
let toolbar = UIToolbar()
toolbar.sizetoFit()
let done2Button = UIBarButtonItem(title: "Select",style: .plain,target: self,action: #selector(doneTapped2))
let space2Button = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,target: nil,action: nil)
toolbar.setItems([space2Button,done2Button],animated: false)
birthdayTextField.inputAccessoryView = toolbar
birthdayTextField.inputView = datePicker
datePicker.datePickerMode = .date
}
override func touchesEnded(_ touches: Set<UITouch>,with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
activeTextField = textField
switch textField {
case stateTextField:
currentArr = state_arr
default:
print("Default")
}
return true
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView,numberOfRowsInComponent component: Int) -> Int {
return currentArr.count
}
func pickerView(_ pickerView: UIPickerView,titleForRow row: Int,forComponent component: Int) -> String? {
return currentArr[row]
}
func pickerView(_ pickerView: UIPickerView,didSelectRow row: Int,inComponent component: Int) {
print("Selected item is",currentArr[row])
activeTextField!.text = currentArr[row]
}
func createtoolbar() {
let toolbar = UIToolbar()
toolbar.barStyle = .default
toolbar.sizetoFit()
let doneButton = UIBarButtonItem(title: "Select",action: #selector(doneTapped))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel",action: #selector(cancelTapped))
toolbar.setItems([cancelButton,spaceButton,doneButton],animated: false)
stateTextField.inputAccessoryView = toolbar
}
@objc func doneTapped() {
activeTextField?.resignFirstResponder()
}
@objc func cancelTapped() {
activeTextField?.resignFirstResponder()
}
override func viewDidLoad() {
super.viewDidLoad()
self.errorLabel.alpha = 0
self.navigationController?.setNavigationBarHidden(false,animated: false)
setUpElements()
createtoolbar()
createDatePicker()
stateTextField.delegate = self
statePickerView.delegate = self
statePickerView.dataSource = self
stateTextField.inputView = statePickerView
}
func setUpElements() {
Utilities.styleFilledButton(signUpButton)
}
func validateFields() -> String? {
if firstNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
lastNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
verifyPasstextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
userNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
stateTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
birthdayTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
{
return "Please fill in all fields."
}
let cleanedPassword = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if Utilities.isPasswordValid(cleanedPassword) == false {
// Password isn't secure enough
return "Please make sure your password is at least 8 characters,contains a special character and a number."
}
if passwordTextField.text != verifyPasstextField.text {
// Password isn't secure enough
return "Passwords do not match."
}
return nil
}
@IBAction func termsButtonTapped(_ sender: Any) {
self.view.endEditing(true)
}
@IBAction func policyButtonTapped(_ sender: Any) {
self.view.endEditing(true)
}
@IBAction func signUpTapped(_ sender: Any) {
self.view.endEditing(true)
verifyState()
self.errorLabel.alpha = 0
let error = validateFields()
if error != nil {
showError(error!)
}
else {
// Create cleaned versions of the data
let firstName = firstNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let lastName = lastNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let username = userNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let state = stateTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let birthday = birthdayTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// Create the user
Auth.auth().createuser(withEmail: email,password: password) { (result,err) in
// Check for errors
if err != nil {
// There was an error creating the user
self.showError("Error creating user")
}
else {
// User was created successfully,Now store the first name and last name
let db = Firestore.firestore()
db.collection("users").document(Auth.auth().currentUser!.uid).setData( ["firstname":firstName,"lastname":lastName,"username":username,"email": email,"birthday":birthday,"state":state,"uid": Auth.auth().currentUser!.uid]) { (error) in
if error != nil {
// Show error message
self.showError("Error saving user data")
}
}
db.collection("usernames").document(username).setData( ["username":username,"uid"
: Auth.auth().currentUser!.uid]) { (error) in
if error != nil {
// Show error message
self.showError("Error saving user data")
}
}
// Transition to the home screen
self.transitionToHome()
}
}
self.view.endEditing(true)
}
}
@IBAction func termsSwitch(_ sender: UISwitch) {
self.view.endEditing(true)
if (sender.isOn == true)
{signUpButton.isEnabled = true}
else if (sender.isOn == false)
{signUpButton.isEnabled = false}
}
func showError(_ message:String) {
errorLabel.text = message
errorLabel.alpha = 1
}
func transitionToHome() {
let HoMetabViewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.hoMetabViewController) as? HoMetabViewController
view.window?.rootViewController = HoMetabViewController
view.window?.makeKeyAndVisible()
}
func verifyState() {
if stateTextField.text == "" {
self.showError("Select a State.")
return
}
}
func verifyPassword() {
if passwordTextField.text == verifyPasstextField.text {
}
else {
self.showError("Passwords do not match.")
return
}
}
@objc func doneTapped2() {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
birthdayTextField.text = formatter.string(from: datePicker.date)
self.view.endEditing(true)
}
}
解决方法
没有开箱即用的解决方案。您已经在使用Auth.auth().createUser
,它将确保电子邮件是唯一的,但用户名别无其他选择。
我认为数据库中的规则将不起作用,因为身份验证和数据库是两个单独的产品,它们不会互相读取数据。数据库规则可以防止访问数据库中的数据,但不能访问身份验证服务,在身份验证服务中,您需要检查用户名。
如果您只是在学习对客户端进行检查的话,我认为这是最简单的选择。虽然就安全性而言,它可能不是100%最佳选择,但它可能是最佳选择。可以通过在每次用户更改用户名时从服务器获取用户ID并仅在用户名不存在时让用户继续使用Auth.auth().createUser
代码来实现。
可以解决您的安全问题的一个更复杂的选择是使用Firebase Cloud Functions。这些是小的代码片段(当前没有swift选项),位于Firebase后端,可在事件或调用发生时运行。 More info on cloud functions.
如果您要走那条路线,则有很多在线指南。每当用户尝试创建新帐户时,我都会通过创建一个function that can be called from the app directly来解决这个问题。我也将传递用户尝试注册的信息,然后在后端功能上检查现有用户名和实际帐户创建(Auth.auth().createUser
),并仅传递回您的应用要求(成功,失败,用户名ID是什么...)。这样可以解决安全性问题,但是要使其工作相当多的努力。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。