Method reauthenticateAndRetrieveDataWithCredential requires credential.
I tried this and it gives me an error:
const user = firebase.auth().currentUser;
const credential = firebase.auth.OAuthCredential;
await user.reauthenticateAndRetrieveDataWithCredential(credential);
await user.updateEmail(email);
return email;
Error message
reauthenticateAndRetrieveDataWithCredential failed: First argument "credential" must be a valid credential.
I only have oath authentication (no email + password). So I can't figure out what credential firebase need. Any help?
Edit:
For some reason my firebase.auth.OAuthCredential or (firebase.auth.AuthCredential) return undefined. User is signedIn/authed.
Ran into this issue in a Vue.js project where we have a firebase.js file that handles module imports and exports of const.
Hoping this will help someone save time when using a similar setup.
File: firebase.js
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
// Initialize Firebase
const app = firebase.initializeApp(options)
export const fb = firebase
export const auth = app.auth()
export const db = app.database()
export const functions = app.functions()
Example of changePassword() in other script file or inside *.vue
import { fb, auth } from './firebase.js'
...
changePassword() {
if (yourFormValidation == true) {
let user = auth.currentUser
const credentials = fb.auth.EmailAuthProvider.credential(
user.email,
this.current_password
)
user.reauthenticateAndRetrieveDataWithCredential(credentials)
.then(() => {
user.updatePassword(this.new_password)
.then(() => {
console.log('your password was successfully changed.')
})
.catch(error => console.log(error))
})
.catch(error => console.log(error.message))
}
}
Where I was running into Cannot read property 'credential' of undefined"...
Importing only { auth } from './firebase.js and then calling auth.EmailAuthProvider().
How to access other classes found on firebase.auth()...
export const fb = firebase in firebase.js
import { fb, auth } from './firebase.js' where you are writing your function
Call fb.auth.EmailAuthProvider.credential() or other class needed
The documentation for reauthenticating the user shows this example:
var user = firebase.auth().currentUser;
var credential;
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateAndRetrieveDataWithCredential(credential).then(function() {
// User re-authenticated.
}).catch(function(error) {
// An error happened.
});
Your code fails to implement the comment correctly. You will need to show a prompt to the user to provide their credentials again, put those into the correct credential object type, and pass that in.
For example:
var credential = firebase.auth.EmailAuthProvider.credential(
email,
password
);
You need to use a subscription to watch for the changes. Use AngularFire to watch for when they are logged in and get the UID (assuming you are using the Authentication login in Firebase so that all data is saved using the UID as the tree path.
You can also add a set timeout to unsubscribe them after a given time
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { switchMap, map } from 'rxjs/operators';
import { Observable, pipe } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private myOAuthSubscription: Subscription;
private myDatasubscription: Subscription;
public userloggedin:boolean = false;
public uid:string = '';
private functionhasrun:boolean = false;
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase,
public _afAuth: AngularFireAuth,
) {
//check that the user is logged in
try {
this.myOAuthSubscription = this._afAuth.authState.subscribe(user => {
if (user && user.uid) {
console.log('loggedin = true');
this.userloggedin = true;
this.uid = String(user.uid);
if(this.functionhasrun==false){
this.functionhasrun = true;
this.funDoDB():
}
} else {
console.log('loggedin = false');
this.userloggedin = true;
this.uid = '';
}
});
} catch (e) {
console.error("fbData_subscription", e);
}
}
ngOnDestroy() {
this.myOAuthSubscription.unsubscribe();
this.myDatasubscription.unsubscribe();
}
private funDoDB(){
if(this.userloggedin == true){
try {
//subscription using AngulaFire
this.myDatasubscription = this._DB.list('myDataPath/' + this.uid).snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
}
npm install --save angularfire2 firebase
npm install -D rxjs#6.2.2 rxjs-compat#6.2.2
Related
I have a service that detects the presence when the user is online, away and offline in firebase.
presence.service.ts
import { Injectable } from "#angular/core";
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFireDatabase } from "#angular/fire/database";
import * as firebase from "firebase/app";
import { tap, map, switchMap, first } from "rxjs/operators";
import { of } from "rxjs";
import { SuperUserService } from "./../services/superuser.service";
#Injectable({
providedIn: "root",
})
export class PresenceService {
constructor(
private afAuth: AngularFireAuth,
private db: AngularFireDatabase,
private superuser: SuperUserService
) {
console.log("Verificação de status em execução");
this.setName();
this.updateOnUser().subscribe();
this.updateOnDisconnect().subscribe();
this.updateOnAway();
}
getPresence(uid: string) {
return this.db.object(`status/${uid}`).valueChanges();
}
getUser() {
return this.afAuth.authState.pipe(first()).toPromise();
}
async setPresence(status: string) {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
status,
timestamp: this.timestamp,
});
}
}
async setName() {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
nome: this.superuser.user.displayName,
});
}
}
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
updateOnUser() {
const connection = this.db
.object(".info/connected")
.valueChanges()
.pipe(map((connected) => (connected ? "online" : "offline")));
return this.afAuth.authState.pipe(
switchMap((user) => (user ? connection : of("offline"))),
tap((status) => this.setPresence(status))
);
}
updateOnDisconnect() {
return this.afAuth.authState.pipe(
tap((user) => {
if (user) {
return this.db
.object(`status/${user.uid}`)
.query.ref.onDisconnect()
.update({
status: "offline",
timestamp: this.timestamp,
});
}
})
);
}
async signOut() {
await this.setPresence("offline");
await this.afAuth.signOut();
}
updateOnAway() {
document.onvisibilitychange = (e) => {
if (document.visibilityState === "hidden") {
this.setPresence("away");
} else {
this.setPresence("online");
}
};
}
}
Firebase path
With this in mind I am wanting to implement a way to bring up how many users are active (online and away) Ex: 15 active users
I tried this, but it only brings me if exist or not
ref.child("status").orderByChild("status").equalTo("online")
.once("value",snapshot => {
if (snapshot.exists()){
// if exist
}
});
If you want to show the number of online users with your current data structure, you can keep the same query as you have but then use snapshot.numChildren() to determine the number of online users it returned.
If you want to show counts of both online and offline users with your current data structure, you can either:
Use a second query for the offline users, and use the same approach as above to get the count.
Use a single read without a condition on the status field, and then count the individual nodes in your application code with:
ref.child("status")
.once("value",snapshot => {
let onlineCount=0, offlineCount=0;
snapshot.forEach((user) => {
const status = user.child("status").val();
if (status === "online) onlineCount++
else if (status === "offline) offlineCount++
else console.error(`Unexpected status '${status}' for user ${user.key}`);
})
console.log(`Online: ${onlineCount}, Offline: ${offlineCount}`);
});
If you're reading all these user profiles to simply show two counters in your app, you're wasting quite a bit of bandwidth (and thus money) for both yourself and your users. Consider storing the actual counts that you want to display in the database itself, and updating them as each user goes online/offline in something like Cloud Functions.
How do we do this now:
const auth = getAuth(firebaseApp);
export async function updateUserEmail(email) {
try {
// let updatedUser = if need access
await auth.currentUser.updateEmail(email);
} catch (e) {
alert(e.message);
throw new Error();
}
}
updateEmail is no longer a method
You need to import updateEmail from the SDK this way now:
import firebase from "firebase/compat/app";
import { getAuth, onAuthStateChanged, updateEmail } from "firebase/auth";
// Initialize Firebase App
const app = firebase.initializeApp(firebaseConfig);
const auth = getAuth(app);
onAuthStateChanged(auth, (user) => {
console.log("Old Email", user.email);
updateEmail(user, "new#email.tld").then(() => {
console.log("email updated");
}).catch((e) => {
console.log(e);
});
});
Also you need to pass the user object itself in the updateEmail function so for testing purpose I've added the code in onAuthStateChanged but you can fetch the object or actually store it when page loads.
I have a Cloud Firestore collection of documents (metadata about files that I've uploaded), and I have a bucket of those files in Firebase Storage.
I want to delete a given file using my Angular app. I can delete the Firestore docs, but I'm unable to delete the corresponding files in Storage.
Compile error: Property 'then' does not exist on type 'Observable<any>'.
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs'; // I was experimenting with onPromise()
import { finalize, tap } from 'rxjs/operators';
import { AngularFireStorage } from '#angular/fire/storage';
import { StateService } from '../services/state.service';
// ...
export class AdminComponent implements OnInit {
isProgressVisible: boolean;
constructor(private storage: AngularFireStorage, public stateService: StateService) {
this.isProgressVisible = false;
}
// ...
deleteFile(file): void {
this.isProgressVisible = true;
let storagePath = file.storagePath; // ex: 'uploads/1598066351161_myfile.txt'
let delimiter = storagePath.indexOf('/');
let docID = file.storagePath.substring(delimiter + 1); // ex: '1598066351161_myfile.txt'
let self = this;
this.stateService.firebase.firestore().collection('files').doc(docID).delete().then(function () {
console.log('cloud firestore doc deleted');
let ref = self.storage.ref(storagePath); // delete the file from Storage
ref.delete().then(function () {
console.log('file deleted from storage!');
}).catch(function (error) {
console.error('Error deleting file from storage:', error);
});
}).catch(function (error) {
console.error('Error deleting cloud firestore doc:', error);
});
}
}
Any help is appreciated!
this.stateService.firebase.firestore().collection('files').doc(docID).delete() returns an observable which is not promise. You should either convert it to promise with toPromise() or use subscribe instead of then.
Check this post for more insights.
Just before adding a new user to firebase, I want to check if the displayName already exists for an other user. I'm storing the displayName in users:
root / users / document *(uid as id)* / fields *like uid, displayName, email, ...*
I've tried it like this:
import { Injectable, NgZone } from '#angular/core';
import { auth } from 'firebase/app';
import { User } from "./user";
import { Router } from "#angular/router";
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
import { AngularFireDatabase } from '#angular/fire/database';
import { ToastController } from '#ionic/angular';
...
constructor(
public firestore: AngularFirestore,
public firebase: AngularFireDatabase,
public ngFireAuth: AngularFireAuth,
public router: Router,
public ngZone: NgZone,
public toastController: ToastController
) {
this.getUserData();
this.newUser = {} as newUser;
}
...
check_displayName() {
var usersRef = this.firestore.collection("users");
usersRef.where(doc.forEach(doc => doc, '==', this.state.displayName)).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty)
return false
} else {
return true
console.log('displayName already exists')
}
})
}
But I get an issue:
ReferenceError: doc is not defined
How can I iterate through the docs when there are unique document-ids (uid)?
Thx for your hints
Can you try this ??
check_displayName(userNameToCheck) {
const usersRef = this.afStore.collection("users");
usersRef.where('displayName', '==', userNameToCheck).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty);
return false;
} else {
console.log('displayName already exists');
return true;
}
});
}
I think you need to get the snapshot first and check if it is empty. There is no meaning in iterating through all docs and increasing your Firestore Reads.
And regarding the forEach(), first you need to get the snapshot and then use something like snapshot.forEach(). But in your case, if the snapshot is empty the username is unique and you did you job with just 1 read cost.
Small Tip: I have not checked if username comparision is case-sensitive. So I keep another field named displayNameLower and then compare that with lower-cased version of username to check.
Something like; .where('displayNameLower', '==', userNameToCheck.toLowerCase())
The solutiion above by Dharmaraj is pretty okay. But I would like to add further to it. We can make use of limit() so that the firebase stops querying once any first matching document from collection is found.If fastens the querying ultimately.
const firebaseAdmin = require('firebase-admin');
firebaseAdmin.initializeApp();
const afStore = firebaseAdmin.firestore();
check_displayName(userNameToCheck) {
const userRef = this.afStore.collection("users");
userRef.where('displayName', '==', userNameToCheck).limit(1).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty)
return false
} else {
console.log('displayName already exists')
return true
}
})
.catch(err => {
console.log('Alert:: error in querying the users document');
});;
}
I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.