.doc().get() 的 Firebase 权限不足

Firebase insufficient permissions for .doc().get()

我正在尝试对我的应用程序中的用户进行身份验证。我有一个登录屏幕,需要一个电子邮件和密码,然后运行这个:

login2(email: string, password: string): Observable<any> {
    const signInObs: Observable<UserCredential> = from(this.fAuth.signInWithEmailAndPassword(email, password));
    
    return signInObs.pipe(take(1), catchError(this.handleError), mergeMap((result: UserCredential) => {
        console.log(`done this ONE... ${result.user.uid}`);
        
        return this.firestore.collection('users').doc(result.user.uid).get().pipe(catchError(this.handleError), mergeMap((doc: DocumentSnapshot<any>) => {
            console.log(`done this two... ${doc.data().name}`);
            
            return result.user.getIdTokenResult(true).then((token: IdTokenResult) => {
                // authenticate the user in the code
                console.log(`done this three.. ${token.claims.admin}`);
                                    
                this.handleAuthentication(
                    result.user.email, 
                    result.user.uid,
                    doc.data().name,
                    token.claims.admin,
                    token.token 
                );
            }).catch(error => {
                throw new Error(error);
            });
        })); 
    }));
}

但是旧的 ERROR FirebaseError: Missing or insufficient permissions. 错误发生在 this.firebase.collection('users').doc(result.user.uid).get() 部分。如果没有该部分,这一切都很好 - 即它登录,给我一个令牌等等。一切正常,只是它不允许我访问该用户记录...

我的数据库中的规则是:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    // match logged in user doc in users collection
    match /users/{userId} {
      allow create: if request.auth.uid != null;
      allow read: if request.auth != null && request.auth.uid == userId;
    }
    
    // match docs in the users collection
    match /users/{userId} {
        allow read, write: if request.auth.uid != null;
    }
  }
}

关于暗示可能是 FireAuth 和 Firestore 模块未正确通信的回复,这就是 app.module 的样子。这些都是app.module.

中对angular射击模块的引用
import { environment } from 'src/environments/environment';
import { AngularFireModule } from '@angular/fire';
import { AngularFireFunctions } from '@angular/fire/functions';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';

@NgModule({
  declarations: [
    // just components
  ],
  imports: [
    // other imports 
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFirestoreModule, 
    AngularFireAuthModule
  ],
  providers: [
        {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true}, 
        AuthenticationService,
        AngularFireFunctions
    ],
  bootstrap: [AppComponent]
})
export class AppModule { }

以及身份验证服务中的构造函数:

constructor(private fAuth: AngularFireAuth, 
            private firestore: AngularFirestore,
            private router: Router) {
            }

我已经 运行 通过模拟器尝试了 id,它似乎应该可以工作。我已经从登录中记录了用户 ID,它与它应该查找的文档名称相同。

这一切看起来应该有效,但实际上没有。我读了很多教程,none 做的事情大相径庭 - 教程之间的差异不一致。

我知道代码的编写方式还不够完美,但我想先让它发挥作用。

我在数据库中的数据结构如下所示:

文件的ID是相关人员的用户ID,我仔细核对了,都是正确的。

仔细检查它是否有效,否则如果我允许通用读写,那么一切都会正常...只有当这些规则出现时它们才不起作用。

您必须指定要匹配的文档,在本例中是所有文档(递归)。这是规则:

  rules_version = '2';
  service cloud.firestore {
  match /databases/{database}/documents {
    
    // match logged in user doc in users collection
    match /users/{userId}/{documents=**} {
      allow create: if request.auth.uid != null;
      allow read: if request.auth.uid == userId;
    }
    
    // match docs in the users collection
    match /users/{userId}/{documents=**} {
        allow read, write: if request.auth.uid != null;
    }
  }
}

评论后编辑: 根据 https://firebase.google.com/docs/firestore/security/rules-structure#version-2 “在安全规则的版本 2 中,递归通配符匹配零个或多个路径项。 match/cities/{city}/{document=**} 匹配任何子集合中的文档以及 cities 集合中的文档。。我猜想上面的代码解决了子集合问题。如果您仍然遇到问题,请分享您的数据结构,以便人们更好地提供帮助。

第二次编辑: 我想问题的发生是因为 request.auth 为空。如果 request.auth.uid 前有 null pointer.So,Firebase 会抛出错误,你必须检查 request.auth 是否为 null。像那样:

  rules_version = '2';
  service cloud.firestore {
  match /databases/{database}/documents {

    // match logged in user doc in users collection
    match /users/{userId}/{documents=**} {
      allow create: if request.auth != null && request.auth.uid != null;
      allow read: if request.auth != null && request.auth.uid == userId;
    }
    
    // match docs in the users collection
    match /users/{userId}/{documents=**} {
        allow read, write: if request.auth != null && request.auth.uid != null;
    }
  }
}

第三次编辑: 我昨天遇到了同样的问题(规则很好,当所有规则都设置为真时它会起作用)但奇怪的是它只发生在移动设备上.我在 GitHub 上搜索后发现了这个问题:https://github.com/angular/angularfire/issues/2838。问题是由 Firebase 版本引起的。我的版本是8.6.2所以我降级到8.6.1,问题就没有了。您可以通过以下方式降级:

npm install firebase@8.6.1

我认为这对 Firebase 来说是一个非常严重的问题,它阻碍了人们使用 Firebase 后端开发和维护项目。我希望这也能解决您的问题。

登录绝对是异步的 - 您知道身份验证实际上已经在您的 .get() 之前完成了吗?这是使用身份验证侦听器有用的地方。简单地请求身份验证 [this.fAuth.signInWithEmailAndPassword(email, password)] 是不够的;实际授权与此不同步。 https://firebase.google.com/docs/reference/js/firebase.auth.Auth

我对 login/logout 使用了一系列异步调用和侦听器 - 我正在将其中的 一些 构建到一个库中,但是所需的侦听器和状态更改是超出图书馆范围。

让我们尝试一种 less-Angular 方法,因为您有一个奇怪的 Observable 对象链,但每个操作只执行一次。因此,我们不会将每个步骤转换为 Observable 并链接它们,而是使用 return 原生 Promise 链,稍后您可以将其转换为 Observable

此外,收集用户的令牌和他们的用户数据的快照可以同时完成,因为它们彼此不依赖。您也不需要强制刷新用户的 ID 令牌,因为它已经作为上一个登录步骤的一部分进行了刷新。

注意: 不鼓励使用 firebase 作为 AngularFirestore 实例的变量名。 firebase 应视为为 Firebase 客户端 SDK 本身保留,以防止混淆。因此,在此代码示例中,我已将其重命名为 firestore,但您也可以使用 db 或其他名称。

login2(email: string, password: string): Promise<void> {
    return this.fAuth.signInWithEmailAndPassword(email, password)
        .then((result) => {
            const userDocRef = this.firestore
                .collection('users')
                .doc(result.user.uid);

            return Promise.all([
                Promise.resolve(result.user),    // User object
                result.user.getIdTokenResult(),  // User's decoded ID token
                userDocRef.ref.get()             // User data in Firestore (from the wrapped Firebase SDK DocumentReference)
            ]);
        })
        .then(([user, tokenData, userDataSnapshot]) => {
            this.handleAuthentication( // <-- if this is a Promise, return it
                user.email, 
                user.uid,
                userDataSnapshot.get('name'), // <- more efficient than snap.data().name
                tokenData.claims.admin,
                tokenData.token
            );
        });
}

要转换为 Observable,您需要使用:

login2(email: string, password: string): Observable<any> {
    const signInPromise = /* ... code from above ... */

    return from(signInPromise);
}