System Information
Strapi Version: 3.6.5
Operating System: MacOS 11.4
Database: SQL
Node Version: 14.17.0
NPM Version: 6.14.13
Hey there,
I wanted to have a function to change the password by passing the old password and a new one. For that, I found this solution from yohanes (https://stackoverflow.com/a/65237275/9476651). Unfortunately, if I want to execute the POST request, I get the error "Error: The model users-permissions can’t be found. It’s coming from this piece of code:
const user = await strapi.query('user', 'users-permissions').findOne({ email: params.identifier });
This is the first out of maximum three times I need to use the users-permissions plugin and I am pretty sure this error will occur at the other usages as well.
Is there anyone who is able to help me?
Have a great day!
Lucas
My full code:
"use strict";
/**
* api/password/controllers/password.js
*/
const { sanitizeEntity } = require("strapi-utils");
const formatError = (error) => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
module.exports = {
index: async (ctx) => {
// const params = JSON.parse(ctx.request.body);
const params = ctx.request.body;
// The identifier is required
if (!params.identifier) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.email.provide",
message: "Please provide your username or your e-mail.",
})
);
}
// The password is required
if (!params.password) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your password.",
})
);
}
// The new password is required
if (!params.newPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your new password.",
})
);
}
if (!params.confirmPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your new password confirmation.",
})
);
}
if (
params.newPassword &&
params.confirmPassword &&
params.newPassword !== params.confirmPassword
) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.matching",
message: "New passwords do not match.",
})
);
} else if (
params.newPassword &&
params.confirmPassword &&
params.newPassword === params.confirmPassword
) {
// Get user based on identifier
const user = await strapi
.query("user", "users-permissions")
.findOne({ email: params.identifier });
// Validate given password against user query result password
const validPassword = await strapi.plugins[
"users-permissions"
].services.user.validatePassword(params.password, user.password);
if (!validPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.invalid",
message: "Identifier or password invalid.",
})
);
} else {
// Generate new hash password
const password = await strapi.plugins[
"users-permissions"
].services.user.hashPassword({
password: params.newPassword,
});
// Update user password
await strapi
.query("users-permissions")
.update({ id: user.id }, { resetPasswordToken: null, password });
// Return new jwt token
ctx.send({
jwt: strapi.plugins["users-permissions"].services.jwt.issue({
id: user.id,
}),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query("user", "users-permissions").model,
}),
});
}
}
},
};```
This part of the code works perfectly fine.
const user = await strapi.query('user', 'users-permissions').findOne({ email: params.identifier });
The issue is with the other places where users-permissions is used. You need to use "user", "users-permissions" instead of only "users-permissions". I modified the code below so it works now.
"use strict";
/**
* api/password/controllers/password.js
*/
const { sanitizeEntity } = require("strapi-utils");
const formatError = (error) => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
module.exports = {
index: async (ctx) => {
// const params = JSON.parse(ctx.request.body);
const params = ctx.request.body;
console.log("params is ", params);
// The identifier is required
if (!params.identifier) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.email.provide",
message: "Please provide your username or your e-mail.",
})
);
}
// The password is required
if (!params.password) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your password.",
})
);
}
// The new password is required
if (!params.newPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your new password.",
})
);
}
if (!params.confirmPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.provide",
message: "Please provide your new password confirmation.",
})
);
}
if (
params.newPassword &&
params.confirmPassword &&
params.newPassword !== params.confirmPassword
) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.password.matching",
message: "New passwords do not match.",
})
);
} else if (
params.newPassword &&
params.confirmPassword &&
params.newPassword === params.confirmPassword
) {
// Get user based on identifier
const user = await strapi
.query("user", "users-permissions")
.findOne({ email: params.identifier });
// Validate given password against user query result password
const validPassword = await strapi.plugins[
("user", "users-permissions")
].services.user.validatePassword(params.password, user.password);
if (!validPassword) {
return ctx.badRequest(
null,
formatError({
id: "Auth.form.error.invalid",
message: "Identifier or password invalid.",
})
);
} else {
// Generate new hash password
const password = await strapi.plugins[
("user", "users-permissions")
].services.user.hashPassword({
password: params.newPassword,
});
// Update user password
await strapi
.query("user", "users-permissions")
.update({ id: user.id }, { resetPasswordToken: null, password });
// Return new jwt token
ctx.send({
jwt: strapi.plugins[("user", "users-permissions")].services.jwt.issue(
{
id: user.id,
}
),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query("user", "users-permissions").model,
}),
});
}
}
},
};
Related
I am working with graphql to signup/sign in. Registration runs smoothly but I am running into this the bcrypt error upon logging back in. When I change it to user.password in the if statement below the loginUser function, it says throws a newUser is undefined error. Where is my error in this one?
Resolvers:
const resolvers = {
Mutation: {
async registerUser(_, { registerInput: { username, email, password } }) {
const previousUser = await User.findOne({ email });
if (previousUser) {
throw new ApolloError(
"A user with this email already exists" + email,
"User_Already_Exists"
);
}
var encryptedPassword = await bcrypt.hash(password, 10);
const newUser = new User({
username: username,
email: email.toLowerCase(),
password: encryptedPassword,
});
const token = jwt.sign(
{ user_id: newUser._id, email },
"this is the secret",
{
expiresIn: "2h",
}
);
newUser.token = token;
const res = await newUser.save();
return {
id: res.id,
...res._doc,
};
},
async loginUser(_, { loginInput: { email, password } }) {
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.model))) {
const token = jwt.sign(
{ user_id: newUser._id, email },
"this is the secret",
{
expiresIn: "2h",
}
);
user.token = token;
return {
id: res.id,
...res._doc,
};
You need to compare to user.password not user.model
newUser isn't defined if the passwords match, use user instead.
async loginUser(_, { loginInput: { email, password } }) {
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
const token = jwt.sign(
{ user_id: user._id, email },
"this is the secret",
{ expiresIn: "2h"}
);
user.token = token; // this isn't used anywhere, why?
return {id: res.id,...res._doc}; // this looks wrong too, res is not in scope
}
}
I'm trying to use NextAuth and Strapi in my app, but NextAuth session only shows email for the user.
When I call the Strapi login API directly:
axios
.post(`${process.env.STRAPI_URL}/api/auth/local`, {
identifier: "email#provider.com",
password: "test123",
})
.then((response) => {
console.log("User profile", response.data.user);
})
I get this object (response.data.user) in console:
{
"id": 4,
"username": "theusername",
"email": "email#provider.com",
"provider": "local",
"confirmed": true,
"blocked": false,
"createdAt": "2022-09-18T17:02:43.581Z",
"updatedAt": "2022-09-27T16:39:22.993Z"
}
But when I try to sign in with NextAuth:
import axios from "axios";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
export default NextAuth({
// Configure one or more authentication providers
providers: [
CredentialsProvider({
name: "Sign in with Email",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
// return (
if (credentials == null) return null;
try {
/* I tried `...rest` but no luck. also console log doesn't work here and I can't see the actual response from the API call */
const { user, jwt } =
(await axios
.post(
`${process.env.STRAPI_URL}/api/auth/local`,
{
identifier: credentials.email,
password: credentials.password,
}
)
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error.response);
throw new Error(error.response.data.message);
})) || null;
return { jwt, ...user };
} catch (error) {
console.warn(error);
// Sign In Fail
// return null;
}
// );
},
}),
],
callbacks: {
session: async ({ session, token }) => {
session.id = token.id;
session.jwt = token.jwt;
return Promise.resolve(session);
},
jwt: async ({ token, user }) => {
if (user) {
token.id = user.id;
token.jwt = user.jwt;
}
return Promise.resolve(token);
},
},
});
I only get email for the user:
{
"user": {
"email": "email#provider.com"
},
"expires": "2022-10-27T20:03:20.177Z",
"id": 4,
"jwt": "somejwtcodethatichangedhereforsecurity"
}
How can I have other properties, like username, in the user returned from NextAuth?
It's possible, by modifying callbacks:
callbacks: {
session: async ({ session, token }) => {
session.id = token.id;
session.jwt = token.jwt;
session.user.username = token.username /* added */
return Promise.resolve(session);
},
jwt: async ({ token, user }) => {
if (user) {
token.id = user.id;
token.jwt = user.jwt;
token.username = user.username /* added */
}
return Promise.resolve(token);
},
},
I am running into a POST http://localhost:4000/ 400 (Bad Request) Error.
I am trying to create a new user with the following frontend.
const REGISTER_USER = gql`
mutation Mutation(
$createUser: CreateUserInput!
) {
createUser(createUserInput: $createUserInput){
email
name
token
password
}
}
`
const Register = () => {
const context = useContext(AuthContext)
let navigate = useNavigate()
const [errors, setErrors] = useState([])
function registerUserCallback() {
console.log("Callback hit")
registerUser()
}
const {onChange, onSubmit, values} = useForm(registerUserCallback, {
name: '',
email: '',
password:'',
confirmPassword: '',
})
const [registerUser, {loading}] = useMutation(REGISTER_USER, {
update(proxy, {data: {registerUser: userData}}) {
context.login(userData)
navigate('/Dashboard')
},
onError({graphQLErrors}) {
setErrors(graphQLErrors)
console.log("Error: " + graphQLErrors)
console.log(graphQLErrors)
},
variables: {createUserInput: values}
})
However, the grapQLErrors is not even being console.logged for some reason. When I run the Mutation via Apollo Studio it works. Any information would be great!
Edit: Network Tab Screenshot:
Adding Code for my httpLink:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const authLink = setContext((_, {headers}) => {
return {
headers: {
...headers,
authorization: localStorage.getItem('token') || ""
}
}
})
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Edit: createUser Mutation seems to be the issue. This is the Network response error: ["GraphQLError: Unknown argument "createUserInput" on field "Mutation.createUser".","
#Mutation((returns) => User)
async createUser(#Arg('data') data:CreateUserInput, #Ctx() ctx: Context) {
const oldUser = await ctx.prisma.user.findFirst({ where: { email: data.email}})
if(oldUser) {
throw new ApolloError('A user is already registered with the email' + data.email, 'USER_ALREADY_EXISTS')
}
var encryptedPassword = await bcrypt.hash(data.password, 10)
const newUser = await ctx.prisma.user.create({
data: {
name: data.name,
email: data.email,
password: encryptedPassword
}
})
return {token: jwt.sign(newUser, 'supersecret')}
}
Here is a screen shot of my Preview in my Network...I really don't get it.
export class CreateUserInput {
#Field((type) => String)
name: string
#Field((type) => String)
email: string
#Field((type) => String)
password: string
I have been working on this for 3 days now and keep getting stuck. I am trying to register a new user and then have it push to a new page after registration.
The error that I am stuck on is the error above. Here is my frontend code.
const REGISTER_USER = gql`
mutation createUser($input: CreateUserInput!) {
createUser(input: $input) {
email
name
password
}
}
`
const Register = () => {
const context = useContext(AuthContext)
let navigate = useNavigate()
const [errors, setErrors] = useState([])
function registerUserCallback() {
console.log("Callback hit")
registerUser()
}
const {onChange, onSubmit, values} = useForm(registerUserCallback, {
name: '',
email: '',
password:'',
})
const [registerUser, {loading}] = useMutation(REGISTER_USER, {
update(proxy, {data: {createUser: userData}}) {
context.login(userData)
navigate('/Dashboard')
},
onError({graphQLErrors}) {
setErrors(graphQLErrors)
console.log("Error: " + graphQLErrors)
},
variables: {CreateUserInput: values}
})
Any information would be great!
I am trying to manually set the status on my formik instance upon receiving an error response from server:
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
onSubmit: async ({ email, password }, { setStatus }) => {
try {
const response = await loginUser({
variables: {
email,
password,
},
});
} catch (error) {
await setStatus({ email: error.message, password: error.message });
console.log(formik.status);
}
formik.resetForm();
},
validationSchema: Yup.object().shape({
email: Yup.string(),
password: Yup.string(),
}),
});
Logging status prints undefined.
Why is status not being set?
Figured it out.
Using resetForm clears both form status and errors.
To get around this I have manually cleared both fields instead:
initialValues={{
email: "",
password: "",
}}
onSubmit={async ({ email, password }, { setStatus, setFieldValue }) => {
try {
const response = await loginUser({
variables: {
email,
password,
},
});
} catch (error) {
setStatus({
email: "Shit",
});
setFieldValue("email", "");
setFieldValue("password", "");
}
}}
validationSchema={Yup.object().shape({
email: Yup.string(),
password: Yup.string(),
})}