prosource

Cloud Firestore: 고유 사용자 이름 적용

probook 2023. 7. 17. 21:13
반응형

Cloud Firestore: 고유 사용자 이름 적용

문제

저는 이 질문을 여러 번 보았지만(Firebase Real-Time Database의 맥락에서도) 설득력 있는 답변을 보지 못했습니다.문제 설명은 매우 간단합니다.

(인증된) 사용자가 아직 사용하지 않은 사용자 이름을 선택하는 방법은 무엇입니까?

먼저, 이유: 사용자 인증 후 고유한 사용자 ID를 가집니다.그러나 많은 웹 앱은 사용자의 개인 데이터(실명과 같은)를 보호하기 위해 사용자가 "표시 이름"(사용자가 웹 사이트에 표시할 방법)을 선택할 수 있도록 합니다.

사용자 모음

다음과 같은 데이터 구조가 주어지면 각 사용자에 대한 다른 데이터와 함께 사용자 이름을 저장할 수 있습니다.

/users  (collection)
    /{uid}  (document)
        - name: "<the username>"
        - foo: "<other data>"

사용자 사용자)를 수 있는 것은 .{uid}) 동일한 것을 저장합니다.name그들의 기록에. " 규칙", "보안 규칙"이할 수 .name이미 다른 사용자가 사용했습니다.

참고: 클라이언트 측 검사가 가능하지만 악의적인 클라이언트가 검사를 생략할 수 있으므로 안전하지 않습니다.

역방향 매핑

인기 있는 솔루션은 역방향 매핑을 사용하여 컬렉션을 만들고 있습니다.

/usernames  (collection)
    /{name}  (document)
       - uid: "<the auth {uid} field>"

이 역방향 매핑을 고려할 때 보안 규칙을 작성하여 사용자 이름이 아직 사용되지 않도록 할 수 있습니다.

match /users/{userId} {
  allow read: if true;
  allow create, update: if
      request.auth.uid == userId &&
      request.resource.data.name is string &&
      request.resource.data.name.size() >= 3 &&
      get(/PATH/usernames/$(request.resource.data.name)).data.uid == userId;
}

사용자가 먼저 사용자 이름 문서를 작성하도록 하려면 다음을 수행합니다.

match /usernames/{name} {
  allow read: if true;
  allow create: if
      request.resource.data.size() == 1 &&
      request.resource.data.uid is string &&
      request.resource.data.uid == request.auth.uid;
}

해결책이 절반은 거기에 있다고 생각합니다.하지만, 아직 해결되지 않은 몇 가지 문제들이 있습니다.

남은 문제/질문

이 구현은 이미 상당히 관련되어 있지만 사용자 이름을 변경하려는 사용자의 문제(레코드 삭제 또는 업데이트 규칙 등 필요)를 해결하지도 못합니다.

또 다른 문제는 사용자가 여러 레코드를 추가하는 것을 방해하지 않는다는 것입니다.usernames수집, 시스템을 방해하기 위해 모든 정상적인 사용자 이름을 효과적으로 캡처합니다.

질문은 다음과 같습니다.

  • 고유한 사용자 이름을 적용할 수 있는 더 간단한 솔루션이 있습니까?
  • 어떻게 스팸을 보낼 수 있습니까?usernames수집을 방지할 수 있습니까?
  • 사용자 이름 검사를 대소문자를 구분하지 않도록 하려면 어떻게 해야 합니까?

나는 또한 존재를 강제하려고 노력했습니다.users 하나의 께.exists()/username 컬렉션에 대한 규칙을 지정한 다음 일괄 쓰기 작업을 커밋합니다. 그러나 이것은 작동하지 않는 것 같습니다("권한 없음 또는 부족" 오류).

다른 참고 사항:클라이언트 측의 검사를 통해 해결책을 본 적이 있습니다. 하지만 이것들은 안전하지 않습니다.악성 클라이언트는 코드를 수정하고 검사를 생략할 수 있습니다.

@asciimike트위터에는 파이어베이스 보안 규칙 개발자가 있습니다.그는 현재 문서의 키에 고유성을 적용할 수 있는 방법이 없다고 말합니다.https://twitter.com/asciimike/status/937032291511025664

때부터firestore는 구글 구글에 을 두고 있습니다.datastore이 문제를 상속합니다.그것은 2008년부터 오랫동안 지속된 요청이었습니다.https://issuetracker.google.com/issues/35875869#c14

▁using다▁by있수니▁however습▁your▁goal▁achieve,▁can달을 사용함으로써 여러분의 목표를 달성할 수 있습니다.firebase functions의 엄격한 그리고엄격한간.security rules.

제가 제안한 전체 솔루션을 미디어에서 볼 수 있습니다.https://medium.com/ @jqualls/firebase-firestore-firestore-contractions-d0673b7a4952

저를 위해 아주 간단한 또 다른 해결책을 만들었습니다.

있습니다usernames고유 값을 저장하기 위한 컬렉션입니다. username문서가 없는 경우 사용할 수 있으므로 프런트엔드에서 쉽게 확인할 수 있습니다.

또한 패턴을 추가했습니다.^([a-z0-9_.]){5,30}$키 값의 유효성을 확인합니다.

Firestore 규칙으로 모든 항목 확인:

function isValidUserName(username){
  return username.matches('^([a-z0-9_.]){5,30}$');
}

function isUserNameAvailable(username){
  return isValidUserName(username) && !exists(/databases/$(database)/documents/usernames/$(username));
}

match /users/{userID} {
  allow update: if request.auth.uid == userID 
      && (request.resource.data.username == resource.data.username
        || isUserNameAvailable(request.resource.data.username)
      );
}

match /usernames/{username} {
  allow get: if isValidUserName(username);
}

사용자 이름이 이미 존재하거나 값이 잘못된 경우에는 방화벽 규칙에서 사용자 문서 업데이트를 허용하지 않습니다.

따라서 사용자 이름에 유효한 값이 있고 아직 존재하지 않는 경우에만 클라우드 기능이 처리됩니다.따라서 서버의 작업량이 훨씬 줄어듭니다.

것은 업데이트하는 입니다.usernames컬렉션:

const functions = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp(functions.config().firebase);

exports.onUserUpdate = functions.firestore
  .document("users/{userID}")
  .onUpdate((change, context) => {
    const { before, after } = change;
    const { userID } = context.params;

    const db = admin.firestore();

    if (before.get("username") !== after.get('username')) {
      const batch = db.batch()

      // delete the old username document from the `usernames` collection
      if (before.get('username')) {
        // new users may not have a username value
        batch.delete(db.collection('usernames')
          .doc(before.get('username')));
      }

      // add a new username document
      batch.set(db.collection('usernames')
        .doc(after.get('username')), { userID });

      return batch.commit();
    }
    return true;
  });

에서 문서를 추가, 업데이트 또는 삭제할 때마다 트리거되는 일련의 클라우드 기능 생성users 을 합니다. 클라우드 함수는 다음 이름의 별도 룩업 테이블을 유지합니다.usernames문서 ID가 사용자 이름으로 설정됩니다.그러면 프런트 엔드 앱이 사용자 이름 모음을 쿼리하여 사용자 이름이 사용 가능한지 확인할 수 있습니다.

클라우드 기능을 위한 TypeScript 코드는 다음과 같습니다.

/* Whenever a user document is added, if it contains a username, add that
   to the usernames collection. */
export const userCreated = functions.firestore
  .document('users/{userId}')
  .onCreate((event) => {

    const data = event.data();
    const username = data.username.toLowerCase().trim();

    if (username !== '') {
      const db = admin.firestore();
      /* just create an empty doc. We don't need any data - just the presence 
         or absence of the document is all we need */
      return db.doc(`/usernames/${username}`).set({});
    } else {
      return true;
    }

  });

  /* Whenever a user document is deleted, if it contained a username, delete 
     that from the usernames collection. */
  export const userDeleted = functions.firestore
    .document('users/{userId}')
    .onDelete((event) => {

      const data = event.data();
      const username = data.username.toLowerCase().trim();

      if (username !== '') {
        const db = admin.firestore();
        return db.doc(`/usernames/${username}`).delete();
      }
      return true;
    });

/* Whenever a user document is modified, if the username changed, set and
   delete documents to change it in the usernames collection.  */
export const userUpdated = functions.firestore
  .document('users/{userId}')
  .onUpdate((event, context) => {

    const oldData = event.before.data();
    const newData = event.after.data();

    if ( oldData.username === newData.username ) {
      // if the username didn't change, we don't need to do anything
      return true;
    }

    const oldUsername = oldData.username.toLowerCase().trim();
    const newUsername = newData.username.toLowerCase().trim();

    const db = admin.firestore();
    const batch = db.batch();

    if ( oldUsername !== '' ) {
      const oldRef = db.collection("usernames").doc(oldUsername);
      batch.delete(oldRef);
    }

    if ( newUsername !== '' ) {
      const newRef = db.collection("usernames").doc(newUsername);
      batch.set(newRef,{});
    }

    return batch.commit();
  });

이것은 사용자 이름이 고유해야 하는 저에게 효율적으로 작동합니다.중복 없이 사용자 이름을 추가하고 편집할 수 있습니다.

참고: 사용자 이름은 항상 소문자여야 합니다. 따라서 대소문자를 구분하여 중복이 발생하지 않습니다.

사용자 컬렉션 만들기:

/users(수집)

/{uid} (document)
      - name "the username"

사용자 이름 컬렉션 만들기:

/사용자 이름(수집)

/{name} (document)
       - uid "the auth {uid} field"

그런 다음 firestore에서 다음 규칙을 사용합니다.

match /databases/{database}/documents {
    
match /usernames/{name} {
  allow read,create: if request.auth != null;
  allow update: if 
        request.auth.uid == resource.data.uid;
}

match /users/{userId}{
    allow read: if true;
    allow create, update: if 
      request.auth.uid == userId && 
      request.resource.data.name is string && 
      request.resource.data.name.size() >=3 && 
      get(/databases/$(database)/documents/usernames/$(request.resource.data.name)).data.uid == userId;
    }
    
  }

는 장니다합을 합니다.usernames각 사용자 이름이 고유한 동일한 컬렉션에서documentID. 이렇게 하면 이미 존재하는 사용자 이름이 데이터베이스에 생성되지 않습니다.

한 가지 가능한 해결책은 모든 사용자 이름을 하나의 문서에 저장하는 것입니다.usernames필드를 선택한 다음 규칙의 집합을 사용하여 문서에 대한 추가만 허용합니다.

match /users/allUsernames {
  function validateNewUsername() {
    // Variables in functions are allowed.
    let existingUsernames = resource.data.usernames;
    let newUsernames = request.resource.data.usernames;
    let usernameToAdd = newUsernames[newUsernames.size() - 1];
    // Sets are a thing too.
    let noRemovals = existingUsernames.toSet().difference(newUsernames.toSet()).size() == 0;
    let usernameDoesntExistYet = !(usernameToAdd in existingUsernames.toSet());
    let exactlyOneAddition = newUsernames.size() == existingUsernames.size() + 1;
    return noRemovals && usernameDoesntExistYet && exactlyOneAddition;
  }
  allow update: if request.resource.data.keys().hasOnly(['usernames']) && validateNewUsername();
}

사용자 이름 -> uid(규칙 집합의 다른 부분을 확인하기 위해)에서 매핑을 만들고 싶다면 단일 문서에서도 가능합니다.문서의 키 집합을 가져와서 위와 동일한 설정 작업을 수행하면 됩니다.

이 답변은 사용자 이름 모음에 여러 개의 레코드를 추가하는 것에 대한 두 번째 우려를 해결합니다.이것이 최선의 방법인지는 잘 모르겠지만, 주어진 사용자가 여러 사용자 이름 문서를 만들지 못하도록 하는 가능한 접근법은 다음과 같은 것을 작성하는 것이라고 생각합니다.onCreate새 사용자 이름 문서를 만들 때 사용자가 기존 사용자 이름 문서를 가지고 있는지 확인하는 클라우드 기능입니다.사용자가 삭제할 경우 클라우드 기능은 이 문서를 삭제하여 악의적인 사용자 이름 주차를 방지할 수 있습니다.

데이터베이스에 사용된 최대 정수 사용자 ID를 다른 컬렉션에 저장합니다.해당 컬렉션을 매번 쿼리하여 최대 사용자 ID를 찾습니다.이 컬렉션에 다른 최대 ID를 저장할 수도 있습니다.다음과 같이 보일 수 있습니다.

MaxIDCollection:
   maxStudentIDDocument={ maxID: 55 } //lets say the max user id in db is 55
   maxCourseIDDocument={ maxID: 77 }

                   

max를 업데이트해야 합니다.새 학생 또는 과정을 추가할 때마다 ID를 입력합니다.나중에 새 학생을 추가할 경우 이 컬렉션을 쿼리하여 "55가 최대값이면 새 학생이 56을 ID로 얻을 수 있습니다."를 알 수 있습니다.

언급URL : https://stackoverflow.com/questions/47405774/cloud-firestore-enforcing-unique-user-names

반응형