How do you access session.user.id in nextauth? - next.js

I am using next-auth version 4.19.2 .
I have a simple component that just displays who they're logged in as:
import { useSession, signIn, signOut } from "next-auth/react"
export default function LoginPlaceholder() {
const { data: session } = useSession();
if(session) {
return <>
Signed in as
userID: {session.user.id} <br/>
name: {session.user.name} <br/>
email: {session.user.email} <br/>
<button onClick={() => signOut()}>Sign out</button>
</>
}
return <>
Not signed in <br/>
<button onClick={() => signIn()}>Sign in</button>
</>
}
My [...nextauth.js] just returns some dummy data:
async authorize(credentials) {
console.log("authorize script running...");
const user = { id: 22, name: 'J Smith', email: 'jsmith#example.com' }
return user;
}
For some reason, my output only displays the name and email, but no id.
Signed in as userID:
name: J Smith
email: jsmith#example.com
I work on a project, where multiple people in an organization could share the same email address (not my idea), so email is not a unique identifier for me. I need the either the ID or the username, as a unique identifier, but I was unsure how to get that.
I also tried passing other data, but the only thing I can get to show up is name and email.
const user = { id: 22, userName: 'jSmith' name: 'J Smith', email: 'jsmith#example.com' }
But again, my components are not getting any values for anything other than name and email.
My question is: How do I modify my code to display the username?

You should use callbacks, something along these lines:
callbacks: {
async jwt({token, user}) {
if (user?.id) {
token.id = user.id
}
if (user?.userName) {
token.userName = user.userName;
}
return token
},
async session({session, token}) {
session.id = token.id;
session.userName = token.userName;
return session;
}
}

Related

Pinia: How to dynamically add new enteries to store state

I have a use case with the pinia in vue 3 that I want to dynamically add new entries to the pinia store using the store actions. for example if I have a state called firstName and if I call a the action of the store it should add new state called lastName in the state as well. Here is what I have tried
import { defineStore } from "pinia";
export const useAdvanceListingsFilterStore = defineStore(
"advance-listing-filters",
{
state: () => {
return {
firstName: "jhon",
};
},
actions: {
setLastName(payload) {
return {
...this.state,
lastName: payload,
};
},
},
}
);
The new state should include the fistName and lastName fields.
The simplest way would by add lastName: null to your state, but I guess it is not what you are trying to achieve.
I have tried to add new items to the state internally using state.$path (see Mutating the state), but the new lastName item was still not accessible outside the state using state.lastName. So I haven't found a way to achieve your goal directly. But there is another way to do it.
You can use a nested object to achieve your goal.
See the playground below.
I should also state, that adding state items dynamically makes your application less predictable.
So, I would rather rethink the design and flow of data.
const { ref, createApp, defineComponent } = Vue
const { createPinia, defineStore, storeToRefs } = Pinia
const useAlertsStore = defineStore("advance-listing-filters",
{
state: () => {
return {
user: { firstName: "John" }
};
},
actions: {
setLastName(payload) {
this.$state.user.lastName = payload;
}
}
}
)
const App = {
setup() {
const store = useAlertsStore()
const { user } = storeToRefs(store)
const lastName = ref('Doe')
return {
store,
user,
lastName
}
}
}
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
<div id="app">
<label>Name: </label> <b>{{store.user.firstName}} {{store.user.lastName}}</b><br /><br />
<label>First Name:</label> <input :value="user.firstName" disabled /><br /><br />
<label>Last Name:</label> <input v-model="lastName" /><br /><br />
<button #click="store.setLastName(lastName)">Set Last Name</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vue-demi#0.13.11/lib/index.iife.js"></script>
<script src="https://unpkg.com/pinia#2.0.30/dist/pinia.iife.js"></script>

How to create a function that returns new session format with extra key value pair

I am using NextJS with NextAuth with google and email providers. Unfortunately, the session returns only few fields that does not include userId of the user from the database.
I created however a function that I intend to use with every getServerSideProps request. The function returns the following:
{
user: {
name: 'daniel sas',
email: 'emailofuser#gmail.com',
image: 'https://lh3.gooleusercontent.com/a/AEdFTp6r44ZwqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'
},
expires: '2022-12-17T20:18:52.580Z'
}
The problem is I am getting an error that does not allow me to pass the props in the page:
Error: Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?
In the function I get the user by the email, and attach the userId.
import { getSession } from "next-auth/react";
import prisma from './prisma'
// This function get the email and returns a new session object that includes
// the userId
export const requireAuthentication = async context => {
const session = await getSession(context);
const errorOrUserNotFound = () => {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
// If there is no user or there is an error ret to signup page
if (!session) {
errorOrUserNotFound();
}
// If the user is not found return same redirect to signup
else {
try {
const user = await prisma.user.findUnique({where: { email: session.user.email }});
if (!user) return errorOrUserNotFound();
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
console.log(newSession);
return {
props: {
session: newSession
}
}
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}
The react component looks like this. In the getServerSideProps i return the await function. The problem is that when I log the prop in the serverside, I get the following:
{
props: { session: { user: [Object], expires: '2022-12-17T20:18:52.580Z' } }
}
However, if i log the props in the clientside, I get an empty object...
//Clientside compoen
import { getSession } from "next-auth/react"
import { Fragment, useState } from "react";
import { requireAuthentication } from "../../lib/requireAuthentication";
import CreateListModal from "./CreateListModal";
const DashboardPage = props => {
const [loading, setloading] = useState(false);
console.log(props);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={props.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
const session = await getSession(context);
const reqAuth = await requireAuthentication(context);
console.log(reqAuth);
return reqAuth
}
export default DashboardPage;

Maximum call stack size exceeded( in Nuxt + Firebase Project)

I'm currently creating an authentication feature in Nuxt and Firebase.
The login and logout process itself can be done and the header display changes accordingly, but there is an error in console when I press the login button.
Error content (in console)
Uncaught RangeError: Maximum call stack size exceeded
at Function.keys (<anonymous>)
code
Header.vue(This is the page containing the login button.)↓
googleLogin () {
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(res => {
this.dialogAuthVisible = false
this.$store.dispatch('auth/setUser',res.user)
}).catch(e => console.log(e))
}
store/auth.js↓
export const strict = false
export const state = () => ({
user: null
})
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
export const getters = {
isAuthenticated (state) {
return !!state.user
}
}
default.vue↓
mounted () {
auth.onAuthStateChanged(user => {
const { uid, displayName, photoURL} = user
if (user) {
this.$store.dispatch('auth/setUser', { uid, displayName, photoURL})
} else {
this.$store.dispatch('auth/setUser', null)
}
})
}
If there's any information I'm missing, please let me know 🙇️.
Please teach me how to do this 🙇️.
I think the problem is in this code lines :
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
There is a loop between this mutations and actions
Instead of setting the entire payload into the store object, I just picked the fields I needed, and that resolved the problem for me.
Before:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
commit('SetAuthUser', data.authUser);
} else {
commit('SetAuthUser', null);
}
}
After:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
const user = data.authUser;
commit('SetAuthUser', {
uid: user.uid,
email: user.email,
emailVerified: user.emailVerified,
displayName: user.displayName,
isAnonymous: user.isAnonymous,
photoURL: user.photoURL,
stsTokenManager: user.stsTokenManager,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
apiKey: user.apiKey,
});
} else {
commit('SetAuthUser', null);
}
}
Inside the mutation, just add the value received from the mutation payload.

Vuex photoURL and displayName have been passed null to SetUser

My setUser
setUser(state, payload) {
state.user = {...payload}
}
my payload in setUser for random data is
user ===
payload{"uid":"pQOQL9AqMHNsozDE2EFGbMfHZlt1","refreshToken":"AEu4IL3c4doh1ON1ywqNEIXPjijktxAyQsYusC5twuvM61bHK6PpLHENyqKRKGCvNPR5IxBRC7JLQhkjv1qqiVUPdatRVM2Q8VdBCnvxyKkBjOEt_kM6bHCiJI6cdESdmFWZf2B7EjG9MwUJ7l8ASOpdbQLLVs9NtuW94dpNg1dkQShtUXB-sVCafvgtSnluGyZSWGhkt8uJ","photoURL":null,"displayName":null,"email":"yyy#test.com"}
My signUserUp
signUserUp(context, payload) {
//name , email , and password are in payload
context.commit("setLoading", true);
context.commit("clearError");
context.commit("setUserAvatar")
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then((data) => {
data.user.updateProfile({
displayName: payload.name ,
photoURL: 'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light'
})
return data
})
.then((data) => {
context.commit("setLoading", false);
db.collection("profilesInfo")
.add({
id: data.user.uid,
registeredMeetups: []
})
.then(function() {
context.commit("setProfilesInfo",
{
id: data.user.uid,
registeredMeetups: []
}
)
console.log("Document successfully written!");
})
.catch(function(error) {
console.error("Error writing document: ", error);
});
context.commit("setUser", {
name: payload.name ,
id: data.user.uid,
photoURL: 'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light',
email: data.user.email
})
router.push("/");
})
.catch(function(error) {
// Handle Errors here.
context.commit("setLoading", false);
context.commit("setError", error);
});
},
In signUserUp
an user will be created and upon creation value of photoURL and displayName get updated by updateProfile
and after that db is connected again in order to create profilesinfo related to the user which is about showing what meetup groups the user is registered already
It works when in my setUser, I set the value of payload inside setUser and photoURL and displayName will be populated right and I can use it in my profile vue component
I debugged everything inside the console and I cannot understand why this part does not pass photoURL and displayName right
context.commit("setUser", {
name: payload.name ,
id: data.user.uid,
photoURL: 'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light',
email: data.user.email
})
Note: I cleared my storage very often
or In second thought, can my issue is because of using persistent data?
const vuexLocalStorage = new VuexPersist({
key: 'devmeetup-it', // The key to store the state on in the storage provider.
storage: window.localStorage, // or window.sessionStorage or localForage
// Function that passes the state and returns the state with only the objects you want to store.
// reducer: state => ({
// keepLoadedMeetups : store.getters.loadedMeetups,
// keepUser: store.getters.user,
// keepProfilesInfo: state.profilesInfo
// // getRidOfThisModule: state.getRidOfThisModule (No one likes it.)
// })
// Function that passes a mutation and lets you decide if it should update the state in localStorage.
// filter: mutation => (true)
})
Note: after reloading and visiting other pages and come back to profile page, the photo is shown I guess the info became available then?
My Profile.vue
<template >
<div>
<v-card
class="mx-auto"
max-width="434"
tile
>
<v-img
height="100%"
src="https://cdn.vuetifyjs.com/images/cards/server-room.jpg"
>
<v-row
align="end"
class="fill-height"
>
<v-col
align-self="start"
class="pa-0"
cols="12"
>
<v-avatar
class="profile"
size="164"
tile
>
<img :src="imgUrl" alt="">
</v-avatar>
</v-col>
<v-col class="py-0">
<v-list-item
color="rgba(0, 0, 0, .4)"
dark
>
<v-list-item-content>
<v-list-item-title class="title">Name: {{owner_name}}</v-list-item-title>
<v-list-item-subtitle>Email: {{user_info.email}}</v-list-item-subtitle>
<template v-if="meetups.length> 0 ">
<v-list-item-subtitle>Meetup organizer :</v-list-item-subtitle>
<v-card color="rgba(255, 0, 0, 0.5)">
<ol start="1" v-for="(meetup,i) in meetups" v-bind:key="i">
<span >{{i+1}}. {{meetup.title}}</span>
</ol>
</v-card>
</template>
<template>
<div v-if="registeredMeetups.length> 0 ">
<v-list-item-subtitle>Meetup Registred :</v-list-item-subtitle>
<v-card color="rgba(255, 0, 0, 0.5)">
<ol start="1" v-for="(meetup,i) in registeredMeetups" v-bind:key="i">
<span >{{i+1}}. {{meetup.title}}</span>
</ol>
</v-card>
</div>
</template>
</v-list-item-content>
</v-list-item>
</v-col>
</v-row>
</v-img>
</v-card>
<!--<h3>orginzer meetups: {{this.meetups}}</h3>
<h3>registered meetups: {{this.registeredMeetups}}</h3>
<h3>All the meetups: {{this.$store.getters.loadedMeetups}} </h3>
<div style="word-wrap: break-word"> {{imgUrl}} </div>-->
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data(){
return {
imgUrl: this.$store.state.user.photoURL
}
},
created(){
// this.$store.subscribe((mutation, state) => {
// if (mutation.type === "setUserAvatar") {
// //debugger; // eslint-disable-line no-debugger
// this.imgUrl = state.user.photoURL
// }
// });
},
computed: {
...mapState({
owner_name: state => state.user.displayName,
user_info: state => state.user
}),
registeredMeetups(){
let rm= this.$store.getters.currentUserProfileInfo.registeredMeetups
let allm = this.$store.getters.loadedMeetups
let meetupsInfo = []
let i , j
console.log("rm and all meetups are " + JSON.stringify(allm))
for (i = 0; i < rm.length; i++) {
console.log("rm=" + rm[i].toString() )
for ( j = 0 ; j < allm.length; j++){
console.log("lm= " + JSON.stringify(allm[j]))
if(allm[j].id == rm[i].toString())
meetupsInfo.push(allm[j])
}
}
console.log("meetupsInfo " + JSON.stringify(this.$store.state.photoURL))
return meetupsInfo
},
meetups(){
return this.$store.getters.loadedMeetups
.filter( meetup => meetup.creatorId === this.$store.getters.user.uid )
},
profilesInfo(){
// let currentUserProfile = this.$store.state.profilesInfo
// .find( userProfile =>
// userProfile.id === this.$store.getters.user.uid )
return this.$store.getters.currentUserProfileInfo
}
}
}
</script>
or May be, using then clause inside another the clause will have different effect ?
please take a look at my signUserUp then clauses.
my github repo
github.com/KickButtowski80/devmeetup/tree/setting-avataaars
please if more info is needed let me know
thank you
I see there is some different parameter passed in setUser()
In your signUserUp(context, payload):
...
context.commit("setUser", {
name: payload.name ,
id: data.user.uid,
photoURL: 'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light',
email: data.user.email
});
router.push("/");
...
You also said :
It works when in my setUser, I set the value of payload inside setUser
and photoURL and displayName will be populated right and I can use it
in my profile vue component
Does it mean you put it in /store/index.js :
setUser(state, payload) {
const {uid, refreshToken, photoURL, displayName, email} = payload;
console.log('user === payload' + JSON.stringify(payload))
console.log('payload detail info ' + payload.uid + " " + payload.refreshToken
+ " " + payload.photoURL + " " + payload.displayName + " " + payload.email )
// payload = {
// displayName:"test7",
// email:"test7#test.com",
// photoURL:"https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light",
// refreshToken:"AEu4IL0tC9-fuEO-KZNwq953YDo2V7FBpjqB62FT6nXJ5d3r5u3Fzk1RYDzbjkO885rz0LrLyvIjHKHIDemiZsVPeio5XPXK5ntuRyFtLYcu-QOV4xnYYMn18mFxjo6P_TeqrnGIBuwpoto0ceTPxNfYFmedNyuxbNIU6MUVRp5WvnI7OWxVO5404RHIsnLrBsABoigDZgxs",
// uid:"XAhAqlBbs5VZCredSDqdWqKze6C3",
// }
state.user = {...{uid, refreshToken, photoURL, displayName, email}}
So, if you uncomment the line it works ?
Try to pass all the params used with the same key:
...
context.commit("setUser", {
uid: data.user.uid, // id => uid,
displayName: payload.name , // name=> displayName
refreshToken: 'your-token',
photoURL: 'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairDreads01&accessoriesType=Prescription01&hairColor=BlondeGolden&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=Hoodie&clotheColor=Gray01&eyeType=Squint&eyebrowType=AngryNatural&mouthType=Sad&skinColor=Light',
email: data.user.email
});
router.push("/");
...
Does it work ?

Strange issue with useQuery: Query arguments not being read

I have a component that passes a string (userToFetch) it as a variable parameter in a parameterized query. The component looks like this:
// pages/index.jsx
import React from 'react';
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
const GET_USERS = gql`
query users ($limit: Int!, $username: String!) {
users (limit: $limit, where: { username: $username }) {
username
firstName
}
}
`;
const Home = () => {
const userToFetch = 'jonsnow';
const {
loading,
error,
data,
} = useQuery(
GET_USERS,
{
variables: { limit: 2, username: userToFetch },
notifyOnNetworkStatusChange: true,
},
);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {JSON.stringify(error)}</p>;
}
return (
<div>
<ul>
{data.users.map(user => {
return <li>{user.username} {user.firstName}</li>;
})}
</ul>
</div>
);
};
export default Home;
And this is how I have configured my Apollo client:
// /apollo-client.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import withApollo from 'next-with-apollo';
import { createHttpLink } from 'apollo-link-http';
import fetch from 'isomorphic-unfetch';
const GRAPHQL_URL = 'https://dev.schandillia.com/graphql';
const link = createHttpLink({
fetch, // Switches between unfetch & node-fetch for client & server.
uri: GRAPHQL_URL
});
// Export a HOC from next-with-apollo
// Docs: https://www.npmjs.com/package/next-with-apollo
export default withApollo(
// You can get headers and ctx (context) from the callback params
// e.g. ({ headers, ctx, initialState })
({ initialState, ctx }) => {
console.log('initialState', initialState);
console.log('ctx', ctx);
return new ApolloClient({
link: link,
cache: new InMemoryCache()
// rehydrate the cache using the initial data passed from the server:
.restore(initialState || {})
})
}
);
The database is a collection of following users:
"users": [
{
"username": "negger",
"firstName": "Arnold",
"lastName": "Schwarzenegger"
},
{
"username": "jonsnow",
"firstName": "Jon",
"lastName": "Snow"
},
{
"username": "tonystark",
"firstName": "Tony",
"lastName": "Stark"
}
]
}
Now, although this should work (it does when I run the query in my graphql playground at https://dev.schandillia.com/graphql), the code runs as if the where clause didn't exist! It just returns all results as if the query being run were:
users {
_id
username
firstName
}
In order to reproduce the issue, visit https://www.schandillia.com. The page ought to display a list with only one element consisting of a matching username-firstName value: jonsnow Jon but it returns two entries, negger Arnold and jonsnow Jon (respecing limit but completely ignoring where). Now, run the same query with jonsnow as a where parameter in https://dev.schandillia.com/graphql:
{
users(where: { username: "jonsnow" }) {
_id
username
firstName
}
}
And the results would be exactly as expected:
{
"data": {
"users": [
{
"_id": "5d9f261678a32159e61018fc",
"username": "jonsnow",
"firstName": "Jon",
}
]
}
}
What am I overlooking?
P.S.: The repo is up for reference at https://github.com/amitschandillia/proost/tree/master/apollo-nextjs.
UPDATE: In order to track down the root cause, I tried logging some values in apollo-client.js:
console.log('initialState', initialState);
Strangely, the output shows the right query, along with the variables being passed, but wrong results:
...
ROOT_QUERY.users({"limit":2,"where":{"username":"jonsnow"}}).0:
firstName: "Arnold"
username: "negger"
__typename: "UsersPermissionsUser"
...
UPDATE: Here's a screenshot of results in my Apollo Client Developer Tools:
The schema generated by Strapi gives the where attribute a Type JSON and hence you have to pass the entire where part in the query variable as JSON since the variables are not getting injected.
# Write your query or mutation here
query users($where: JSON) {
users(where: $where) {
username
firstName
}
}
And the variables would look like:
{"where": {"username": "jonsnow"}}

Resources