Access the error response without modifying the Amplify library - javascript

I am using the AWS Amplify library to handle the login on my React app. This works by wrapping the entire app in a HOC, and I have added custom UIs for the login screens. By default Amplify displays error messages from Cognito in a toast but I would like to display these as plain text within my custom UI.
I have raised this issue on the Amplify repo and have been informed there is no way to customise the error messages but it is currently a feature request. In the meantime, I think there must be a workaround.
There are 3 ways I believe I might be able to access the error message in React:
When an error is received Amplify sets it in the state of 'Authenticator', which is the parent component (created by the HOC) of my custom UI. Without modifying the actual library I can't pass this down as props or pass it into context/redux but is there some hack to be able to access this?
The request to Cognito is handled by a fetch request generated from the library but I can see the response in the console. A 400 response with the error message in the body of the response. Is there a way to set up some kind of event listener to get the error message off the http response?
The library also generates a toast when an error message is returned. I am currently passing the HOC a css theme (display: none) to hide this. Is there a way to listen for the creation of the toast component and get the message off the span element that lays inside?
Any advice would be greatly appreciated.
Matthew

I found two ways to solve this issue.
Firstly, if you are only interested in ‘signIn’, ‘signUp’ or ‘signOut’ events. You can use the ‘Hub’ utility to listen for an 'auth' event before you attempt an authentication action like signIn and then set the error message to state. Something like this:
import { Hub } from "aws-amplify";
handleLogin = e => {
Hub.listen("auth", res => {
const errorMsg = res.payload.data.message ? res.payload.data.message : null;
this.setState(prevState => ({...prevState, errMsg: errorMsg}));
})
this.signIn(e);
}
Alternatively, if like me you want the error messages off confirm sign in or forgot password events. You can use the Auth API and catch the error message. You have to figure out what needs to be passed into each method but the docs are quite informative. Here's an example for the confirm sign in:
import { Auth } from 'aws-amplify';
confrimSignIn = () => {
Auth.confirmSignIn(this.props.authData, this.inputs.code, 'SMS_MFA')
.then(() => this.changeState('signedIn'))
.catch(err => this.setState({errMsg: err.message}));;
}

Related

Why actions in SveltKit give "Error: Cannot prerender pages with actions"?

I have a SvelteKit application, just following the example from the docs https://learn.svelte.dev/tutorial/named-form-actions, the problem is that everything works until I try to write an action:
at: +page.server.js
export const actions = {
default: async () => {
console.log('test')
}
};
vite immediately fails with:
"Cannot prerender pages with actions"
Error: Cannot prerender pages with actions
at render_page (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/page/index.js:87:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async resolve (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/index.js:356:17)
at async respond (file:///mydir/node_modules/#sveltejs/kit/src/runtime/server/index.js:229:20)
at async file:///mydir/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:444:22
Probably I'm missing some configuration or forgot some basics, any idea?
The docs simply state:
Pages with actions cannot be prerendered, because a server must be able to handle the action POST requests.
The assumption is probably that a form action should have an effect on the page when submitted, which would not be possible when a static HTML page is served every time.
You could try to separate any logic to an API endpoint that is not associated with your prerendered page. It depends on what you are trying to do here, maybe the page should simply not be prerendered at all.
This means that +page.ts should set:
export const prerender = false;

Custom logger in Firestore to create statistics on operations?

How can I sniff all traffic in Firestore JS SDK?
To explain in more detail, the following code enables verbose logging into console.
import { setLogLevel } from 'firebase/firestore'
setLogLevel('debug')
Now, everytime a network communication happens, in the console there are outputs like:
[2022-09-28T12:00:21.878Z] #firebase/firestore: Firestore (9.6.10): Connection WebChannel received:
{"documentChange":{"document":{"name":"projects/mu-project/databases/(default)/documents/my_collection/sHM7OvtpEygAKQBawxha","fields":{ ...
Is it possible to subscribe to such logs with a custom callback, instead of printing it in console, so I can analyze the communication?
Something like:
import { setLogLevel, setLogger } from 'firebase/firestore'
setLogLevel('debug')
setLogger((message, payload) => {
// custom stuff
})
It is intended to create statistics on writes and reads, that are intended to be used on FE in realtime.
It's possible in that you can build your own version of the SDK and make it do whatever you want. It's open source.
Otherwise, no, it's not possible.
You're probably better off instrumenting your own application code to make it capture the things it's doing with Firestore.
I have found onLog function which can be used to register a callback on every produced log message. However, it does not solve the question precisely, because the function does not intercept the logging, and therefore, cannot mute the output to the console. It will produce a lot of debug messages which is not desired in the production deployment.
import { onLog, setLogLevel } from 'firebase/app'
setLogLevel('debug') // must be used to track the traffic
onLog((args) => {
console.log('Custom log callback: ', args)
}, {level: 'debug'})
Also note that onLog must be called after the Firestore component is initialized.

Authentication with Firebase and context API in react give me a warning. Is this the right approach?

I am trying to set up an authentication on my web application in React using firebase and Context API.
I am using Context API since as long as I understood I cannot save my jwt token in local storage in order to not be vulnerable to XSS attack and at the moment I do not want to use Redux.
in my App.js I have:
const {setUserInfo} = useContext(userInfoContext);
useEffect(() => {
auth.onAuthStateChanged(user => {
if (user) {
setUserInfo({jwtToken: user.za});
} else {
setUserInfo({jwtToken: null});
}
console.log(user);
});
}, [setUserInfo]);
The methos "auth.onAuthStateChanged" is triggered every time I logged in or I logged out using firebase.auth.
The compiler tell me that to eliminate the warning I should have "[setUserInfo]" instead of "[]". However, doing as he say, the method setUserInfo is executed twice. There is a better way to achieve the result without a warning?
Your problem is that you don't clean up your effect when it is recomputed. As soon as you add setUserInfo to the dependency array, the effect is executed whenever its value changes. This means that you could potentially register many auth.onAuthStateChanged if the value of setUserInfo changes.
auth.onAuthStateChanged returns an unsubscribe function. You can simply return this function inside your effect, which will make react execute the unsubscribe function whenever the hook is executed again and prevent you from having multiple active listeners. I suggest you read more about this topic here.

Auth0 client is null in Vue SPA on page refresh

I have a Vue SPA based on one of Auth0's quickstart apps (https://github.com/auth0-samples/auth0-vue-samples). Everything works fine out of the box, but as soon as I try using the Auth0 client in my component code I run into problems. I followed the "Calling an API" tutorial (https://auth0.com/docs/quickstart/spa/vuejs/02-calling-an-api), which unhelpfully only shows how to call an API using a button. What I want to do is trigger an authenticated call to my API on initial page load so that I can ensure certain data exists in my own API (or create it if it does not). This seems like it should be pretty straightforward. I just throw this code in my created hook of my Vue component:
await this.$auth.getTokenSilently().then((authToken) => {
// reach out to my API using authToken
});
This actually works fine if the app hot reloads from my npm dev server, it reaches out to my API, which authorizes the request using the token, and sends back the correct data. The problem is when I manually reload the page, which causes this:
Uncaught (in promise) TypeError: Cannot read property 'getTokenSilently' of null
at Vue.getTokenSilently (authWrapper.js?de49:65)
at _callee$ (App.vue?234e:49)
Inside the authWrapper.js file (where the Auth0 client lives), the function call is here:
getTokenSilently(o) {
return this.auth0Client.getTokenSilently(o);
}
When I debug the call, "auth0Client" doesn't exist, which is why it's failing. What I can't understand is the correct way to ensure it does exist before I attempt to make the call. There's nothing in the samples that indicates the right way to do this. I tried putting my component code in different components and different Vue lifecycle hooks (created, beforeMount, mounted, etc), all with the same result. The client becomes available after 800 ms or so, but not when this code executes.
This is clearly a timing problem, but it's not clear to me how to tell my component code to sit and wait until this.auth0Client is non-null without doing something horrible and hacky like a setInterval.
I figured out a workaround for now, which I'll add as an answer in case anyone else has this issue, although it's not really the answer I want. Per the authGuard, you can use the exported "instance" from the authWrapper and watch its "loading" flag before executing your code that depends on the auth0Client being ready, like this:
import { getInstance } from "./auth/authWrapper";
// ... Vue component:
created() {
this.init(this.doSomethingWithAuth0Client);
},
methods: {
init(fn) {
// have to do this nonsense to make sure auth0Client is ready
var instance = getInstance();
instance.$watch("loading", loading => {
if (loading === false) {
fn(instance);
}
});
},
async doSomethingWithAuth0Client(instance) {
await instance.getTokenSilently().then((authToken) => {
// do authorized API calls with auth0 authToken here
});
}
}
It's hardly ideal, but it does work.

combining graphql query with a subscription

I am using graphql subscriptions to get a stream of live messages. The question I have is how to combine this with a query as there are messages from the past.
My example is I have a MessagesQuery component that fetches all previous messages, but to display newer messages I am using a MessageSubscription component.
Can anyone explain the best approach for this? I found this GitHub issue but unfortunately no feedback has been. given.
That issue describes the typical approach -- use the Query component or useQuery hook and call subscribeToMore to tack on a subscription. That user's problem seemed to be that the subscription unsubscribes on unmount but that is appropriate and expected behavior.
Example usage:
const { subscribeToMore, ...result } = useQuery(MESSAGES_QUERY, otherOptions)
subscribeToMore({
document: MESSAGES_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
// merge the subscription data into the query result
},
})
See the docs for additional details.

Categories

Resources