Ionic Capacitor storage GET is not working as expected - ionic-framework

I am using capacitor storage where I am trying to set user token. When I set token and after that I am trying to access it, everything works fine.
public async getToken(){
Storage.get({
key: 'token'
}).then(r => r.value)
}
public async setToken(token: string): Promise<void> {
return await Storage.set({
key: 'token',
value: token
}).then(() => this.getToken())
}
but when I try to access it before setting, my app stucks becouse of infinite loop.
Screenshot
anyone met with something like this ?

Related

Firebase Functions Exception "unauthenticated" but function does run

I am implementing Facebook Sign In with Firebase in my Flutter App, and when a user signs in with Facebook Sign In for the first time I am changing the "emailverified" property to true. I do this through functions with the following code:
if (facebookUser.user?.emailVerified == false) {
try {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('verifyFacebook');
await callable.call();
} on FirebaseFunctionsException catch (e) {
print(e.code);
print(e.plugin);
print(e.message);
}
}
where verifyFacebook is:
exports.verifyFacebook = functions.auth.user().onCreate(async (snap) => {
return await admin.auth().updateUser(snap.uid, {
emailVerified: true
});
})
What I do not understand is that a Firebase Function Exception of "unauthenticated" is printed in the console, but when I sign in for the second time the "emailverified" property is changed to true (meaning that the Firebase Function was executed properly)! How could this happen if in the try-catch part I was thrown an error?
I searched for other answers such as adding a permission in Google Cloud Console but none helped me :(
Thanks in advance!
In Google Cloud Console I added "allUsers" in the Permission property, and I expected my app to work fine without the "unauthenticated" error. However, the issue persisted.
The verifyFacebook function triggers every time a user is created with Firebase Authentication and is not a callable function. You don't have to call the function yourself. When you try to call it, the callable function does not exists and hence you get an error but the function triggered by Firebase Auth does run.

how to fix UNAUTHENTICATED error while trying to create an asset report using PLAID? Plaid createAssetReport using cloud function

I'm trying to create an asset report from Plaid, I'm using Cloud Functions for these calls, all the other cloud functions work (link, transactions, balance) but assetReportCreate fails and return UNAUTHENTICATED

The Plaid logs do not show the call...so I guess something is wrong in my Cloud Function, but the Google Console Logs show status 200.
has anybody experienced the same issue and know how to fix it?
PS: just double checked today to make sure I'm authenticated and I am...
//create ASSET report
exports.createAssetReport = functions.https.onCall(async (data, context) => {
const accessToken = data.accessToken;
const daysRequested = data.daysRequested;
// const options = {
// client_report_id: '123',
// webhook: "https://www.example.com", //to let you know when report is ready, get link from cloud console
// };
const configuration = new Configuration({
basePath: PlaidEnvironments[functions.config().app.env],
baseOptions: {
headers: {
"PLAID-CLIENT-ID": functions.config().plaid.client_id,
"PLAID-SECRET": functions.config().plaid.secret,
},
},
});
const plaidClient = new PlaidApi(configuration);
//call the createLinkToken METHOD of the plaidClient instance!
return plaidClient
.assetReportCreate({
access_tokens: [accessToken],
days_requested: daysRequested,
//options,
})
.then((response) => {
const assetReportId = response.data.asset_report_id;
const assetReportToken = response.data.asset_report_token;
return assetReportToken; //token is needed to retrieve the report via //pdf/get
})
.catch((err) => {
console.log(err);
throw new functions.https.HttpsError(
"internal",
" Unable to create asset report: " + err
);
});
});
I found that for whatever reason the function wasn't open for all users to invoke, adding a principal with role: "cloud functions invoker" fixed the issue, shame on you google cloud, I spent two full days on this!! jk, thanks for your services but fix these small details and you could become the #1 cloud provider

attach accessToken using getSession from next-auth and #urql/exchange-auth

I'm using NextAuth for authentication and URQL's authExchange. The idea is to attach an accessToken in the Authorization header.
// ...
import { authExchange } from '#urql/exchange-auth'
import { getSession } from 'next-auth/react'
getAuth: async ({ authState }) => {
// for initial launch, fetch the auth state from storage (local storage, async storage etc)
if (!authState) {
const session = await getSession()
if (session && session.user.accessToken) {
return { token: session.user.accessToken }
}
return null
}
return null
},
When I start the server, it prints [next-auth][error][CLIENT_FETCH_ERROR] https://next-auth.js.org/errors#client_fetch_error request to http://localhost:4200/api/auth/session failed, reason: connect ECONNREFUSED 127.0.0.1:4200
The error is caused by getSession(), but why does that happen and is there any potential fix?
It sounds like NEXTAUTH_URL env variable was either not set, or set to an incorrect url. is you local dev server running at localhost:4200?
If not, you have to update your NEXTAUTH_URL env var.
Also see https://next-auth.js.org/getting-started/example#extensibility about making the accessToken available in your session state. Theres some more work you have to do in the callbacks in your pages/api/auth/[...nextauth].js in order to have your accessToken available there where you expect it.
I ran into a similar issue and realized that the error is thrown whenever the function is called client-side. In that case there is no request object and the getSession function throws an error.
I managed to stop the error by only providing arguments to the function if req exists:
const sessionProps = req ? { req } : undefined;
const session = await getSession(sessionProps);
However, for me that still does not provide me with the accessToken in the session for some reason. It seems like the accessToken is not attached to the session in my case.

Setting custom claims for Firebase auth from flutter

I'm using Firebase auth for an app, but as part of user creation I need to set some custom claims.
I've written a cloud function to set the claims when a user is created:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(user => {
let customClaims;
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
})
.then(() => {
// Update real-time database to notify client to force refresh.
const metadataRef = admin.database().ref("metadata/" + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
return metadataRef.set({
refreshTime: new Date().getTime()
});
})
.then(() => {
return admin.auth().getUser(user.uid);
})
.then(userRecord => {
console.log(userRecord);
return userRecord.toJSON();
})
.catch(error => {
console.log(error);
});
});
When I print out to the console the userRecord I can see the custom claims are set correctly.
Then in flutter I get the token from the created user, but it then doesn't seem to have the custom claims attached.
I'm using this code to create the user and print the claims in flutter
Future<FirebaseUser> signUp({String email, String password}) async {
final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
email: email,
password: password,
)).user;
IdTokenResult result = await (user.getIdToken(refresh: true));
print('claims : ${result.claims}');
return user;
}
If I inspect the token itself in a jwt debugger I can see its not got the custom claims on it.
Is it that I need some additional steps to try and get an updated token once the claims have been set?
I've tried user.reload() and user.getIdToken(refresh: true) but they don't seem to help.
Any ideas on how to get the token that has the custom claims?
For future reference, I managed to get this working with Doug's suggestions.
Here's my firebase sdk admin function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestore = admin.firestore();
const settings = {timestampsInSnapshots: true};
firestore.settings(settings);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(async user => {
// Check if user meets role criteria:
// Your custom logic here: to decide what roles and other `x-hasura-*` should the user get
let customClaims;
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
})
.then(async () => {
await firestore.collection('users').doc(user.uid).set({
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
})
.catch(error => {
console.log(error);
});
});
Then on the flutter side of things
Future<FirebaseUser> signUp({String email, String password}) async {
final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
email: email,
password: password,
)).user;
currentUser = user;
await waitForCustomClaims();
return user;
}
Future waitForCustomClaims() async {
DocumentReference userDocRef =
Firestore.instance.collection('users').document(currentUser.uid);
Stream<DocumentSnapshot> docs = userDocRef.snapshots(includeMetadataChanges: false);
DocumentSnapshot data = await docs.firstWhere((DocumentSnapshot snapshot) => snapshot?.data !=null && snapshot.data.containsKey('createdAt'));
print('data ${data.toString()}');
IdTokenResult idTokenResult = await (currentUser.getIdToken(refresh: true));
print('claims : ${idTokenResult.claims}');
}
Hopefully this will help somebody else looking to do similar.
The code you're showing is likely trying to get custom claims too soon after the account is created. It will take a few seconds for the function to trigger after you call auth.createUserWithEmailAndPassword. It runs asynchronously, and doesn't at all hold up the process of user creation. So, you will need to somehow wait for the function to complete before calling user.getIdToken(refresh: true).
This is precisely the thing I address in this blog post. The solution I offer does the following:
Client: Creates a user
Client: Waits for a document with the user's UID to be created in Firestore
Server: Auth onCreate function triggers
Server: Function does its work
Server: At the end, function writes data to a new document with the new user's UID
Client: Database listener triggers on the creation of the document
Then, you would add more more step on the client to refresh the ID token after it sees the new document.
The code given in the post is for web/javascript, but the process applies to any client. You just need to get the client to wait for the function to complete, and Firestore is a convenient place to relay that information, since the client can listen to it in real time.
Also read this post for a way to get a client to refresh its token immediately, based on claims written to a Firestore document.
Bottom line is that you're in for a fair amount of code to sync between the client and server.

Ionic 2 - Storage always empty until reloading the app

I am currently building an application with Ionic 2 and using the Storage plugin to hold my values which are pretty much just an API Token and user profile since the application pulls all data from an API.
I am testing the application via ionic serve because no native functions are used but now I am facing the problem that every time I store a value in the Storage the value is not accessible until I reload the app which is kind of annoying because after the user logs in he gets redirected to a page that requires the API token which is not available until I reload the app so the whole thing gets stuck in a loop.
Ionic Storage is using IndexedDB in the browser where I can see that the values have been stored when I check them with Chrome Developer tools.
I have been trying to figure out the issue but can't find any reason why the storage values are not available until reloading the app.
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { HttpClientService } from './http-client-service';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthService {
constructor(public events: Events, public storage: Storage, public http: HttpClientService) {
//
}
login(user) {
var response = this.http.post('login', {
email: user.email,
password: user.password,
});
response.subscribe(data => {
this.storage.set('api_token', data.token);
console.log('raw : ' + data.token); // shows the api token
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
return response;
};
}
Edit: I managed to track down the issue to the storage running async which results in the token not being added to the headers.
createAuthorizationHeader(headers: Headers) {
// this does add the header in time
localStorage.setItem('api_token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYXBpLndpaHplLmRldi9sb2dpbiIsImlhdCI6MTQ4MTE4MzQyOCwiZXhwIjoxNDgxMTg3MDI4LCJuYmYiOjE0ODExODM0MjgsImp0aSI6IjdlNTE1WUEwWmE4NWc2QjUiLCJzdWIiOiIxIiwidXNlciI6eyJpZCI6MX19.T4KpqgCB8xU79vKyeLG4CJ0OHLpVI0j37JKIBJ_0CC4');
headers.append('Authorization', 'Bearer ' + localStorage.getItem('api_token'));
// this does not add the header in time
return this.storage.get('api_token').then((value) => {
headers.append('Authorization', 'Bearer ' + value);
});
}
getHeaders(path) {
let headers = new Headers();
headers.set('Accept', 'application/json');
headers.set('Content-Type', 'application/json');
if(!this.isGuestRoute(path)) {
this.createAuthorizationHeader(headers);
}
return new RequestOptions({ headers: headers });
}
get(path: string) {
return this._http.get(this.actionUrl + path, this.getHeaders(path))
.map(res => res.json())
.catch(this.handleError);
}
Alright, looked in the ionic docs and I do understand why you put them both underneath eachother since they also display it like that in the docs.
But Storage.set(key, value) :
Returns:
Promise that resolves when the value is set
This means that you cannot use it the way you are using it (hence why they added a comment with //or ....
Since resolving a Promise is asynchronous.
If you want to use the value like you're currently using it (which seems a bit odd but probably for you to test if the value is set correctly) you should use
this.storage.set('api_token', data.token).then(() => {
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
console.log('raw : ' + data.token); // shows the api token
If you would like some more information about why this happens, check out this SO answer (I prefer second one) Asynchronous vs synchronous execution, what does it really mean?