typescript - Firebase permisos insuficientes para .doc().get()
Estoy tratando de autenticar a un usuario en mi aplicación. Tengo una pantalla de inicio de sesión que solicita un correo electrónico y una contraseña y luego ejecuta esto:
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);
});
}));
}));
}
Pero el viejo error ERROR FirebaseError: Missing or insufficient permissions. ocurre con respecto a la parte this.firebase.collection('users').doc(result.user.uid).get(). Todo esto funciona perfectamente sin esa sección, es decir, inicia sesión, me da un token y así sucesivamente. Todo funciona bien excepto que no me permite acceder a ese registro de usuario...
Las reglas en mi base de datos son:
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;
}
}
}
En referencia a una respuesta que sugería que podría ser que los módulos FireAuth y Firestore no se estén comunicando correctamente, así es como se ve el app.module. Estas son todas las referencias al módulo Angular Fire en el app.module.
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 { }
Y el constructor en el servicio de Autenticación:
constructor(private fAuth: AngularFireAuth,
private firestore: AngularFirestore,
private router: Router) {
}
He intentado ejecutar el ID a través del emulador y parece que debería funcionar. He registrado el ID de usuario desde el inicio de sesión y es idéntico al nombre del documento que debería estar buscando.
Todo esto parece que debería funcionar, pero no lo hace. He leído un montón de tutoriales y ninguno hace algo muy diferente, y las diferencias son inconsistentes entre los tutoriales.
Reconozco que el código no está perfecto en la forma en que está escrito todavía, pero quería hacerlo funcional primero.
Mi estructura de datos en la base de datos se ve así:
El ID de los documentos es el ID de usuario de la persona correspondiente, he verificado dos veces que todos sean correctos.
Verificándolo dos veces funciona, de lo contrario, si permito lectura y escritura universal, todo funciona bien... solo es cuando entran en juego esas reglas que no funcionan.
Respuesta #1
Debes especificar qué documento deseas coincidir, en este caso son todos los documentos (recursivamente). Aquí está la regla:
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;
}
}
}
EDITAR después del comentario: Según https://firebase.google.com/docs/firestore/security/rules-structure#version-2 "En la versión 2 de las reglas de seguridad, los comodines recursivos coinciden con cero o más elementos de la ruta. match/cities/{city}/{document=**} coincide con documentos en cualquier subcolección, así como con documentos en la colección cities.". Supongo que el código anterior resuelve el problema de las subcolecciones. Si todavía encuentras problemas, amablemente comparte tu estructura de datos para que la gente pueda ayudar mejor.
SEGUNDA EDICIÓN: Supongo que el problema ocurre porque request.auth es nulo. Firebase lanzará un error si hay un puntero nulo. Así que antes de request.auth.uid, debes comprobar si request.auth es nulo o no. Así:
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;
}
}
}
TERCERA EDICIÓN: Ayer me encontré con el mismo problema (las reglas están bien y funciona cuando todas las reglas están configuradas en verdadero), pero estaba ocurriendo solo en dispositivos móviles, curiosamente. Encontré este problema en GitHub después de buscar: https://github.com/angular/angularfire/issues/2838. El problema fue causado por la versión de Firebase. Mi versión era 8.6.2, así que la degradé a 8.6.1, y el problema desapareció. Puedes degradarla de la siguiente manera:
npm install [email
Creo que este es un problema muy serio para Firebase, impide que las personas desarrollen y mantengan proyectos con el backend de Firebase. Espero que esto también resuelva tu problema.
Respuesta #2
Intentemos un enfoque menos Angular porque tienes una cadena extraña de objetos Observable pero cada operación se realiza solo una vez. Así que en lugar de convertir cada paso en un Observable y encadenarlos, usaremos y devolveremos la cadena nativa de Promise, que puedes convertir en un Observable más tarde.
Además, recolectar el token de un usuario y una instantánea de sus datos de usuario se puede hacer al mismo tiempo ya que no dependen uno del otro. Tampoco necesitas forzar la actualización del token de ID del usuario ya que ya se actualizó como parte del paso de inicio de sesión anterior.
Nota: Se desaconseja usar firebase como el nombre de variable de una instancia de AngularFirestore. firebase debe considerarse reservado para el propio Firebase Client SDK para evitar confusiones. Así que en este ejemplo de código, lo he renombrado a firestore, aunque también podrías usar db u otro nombre.
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
);
});
}
Para convertir a un Observable usarías:
login2(email: string, password: string): Observable<any> {
const signInPromise = /* ... code from above ... */
return from(signInPromise);
}
