I am using server middleware to handle the POST from a contact form.
The problem is, it doesn't seem like the middleware file I created runs. I added breakpoints using VS Code, and they don't fire in the contact.js, but they do fire in the contact.vue (in VS Code I made sure to add the breakpoints in the appropriate client/server side environments). Even the console log info I have in the contact.js doesn't appear. It is like the file is being missed.
What happens is the POST call is made and stays open, with no response. The POST'd attributes and values are all there.
I suspect that the createTransport({sendmail: true}) to fail as I don't have sendmail on my development system, but no error is thrown.
Can you see if there is anything I am missing? All I want to do is simply send an email when the form is submitted, but it seems so difficult!
I was guided by https://blog.lichter.io/posts/emails-through-nuxtjs/
All packages are installed.
This is how I have it set up...
nuxt.config.js
export default {
.
.
.
serverMiddleware: [
'~/api/contact'
],
.
.
.
}
pages/contact.vue
<template>
.
.
.
<form class="form-email row mx-0"
data-success="Thanks for your enquiry, we'll be in touch shortly."
data-error="Please fill in all fields correctly."
#submit.prevent="submitForm">
<div class="col-md-6 col-12 text-xl"> <label>Your Name:</label> <input v-model="name" type="text" name="name"
class="validate-required"> </div>
<div class="col-md-6 col-12 text-xl"> <label>Email Address:</label> <input v-model="email" type="email" name="email"
class="validate-required validate-email"> </div>
<div class="col-md-12 col-12 text-xl"> <label>Message:</label> <textarea v-model="message" rows="4" name="message"
class="validate-required"></textarea> </div>
<div class="col-md-5 col-lg-4 col-6"> <button type="submit" class="btn btn--primary type--uppercase">Submit</button> </div>
</form>
.
.
.
</template>
<script>
export default {
data () {
return {
name: '',
email: '',
message: ''
}
},
mounted() {
mr.documentReady($);
mr.windowLoad($);
},
methods: {
async submitForm () {
try {
await this.$axios.$post('', {
name: this.name,
email: this.email,
msg: this.message
});
await new Promise(resolve => setTimeout(resolve, 2500));
} catch (e) {
console.error(e);
}
}
}
}
</script>
api/contact.js
const express = require('express')
const nodemailer = require('nodemailer')
const validator = require('validator')
const xssFilters = require('xss-filters')
const app = express()
app.use(express.json())
app.get('/', function (req, res) {
res.status(405).json({ error: 'sorry!' })
})
app.post('/', function (req, res) {
const attributes = ['name', 'email', 'message'] // Our three form fields, all required
// Map each attribute name to the validated and sanitized equivalent (false if validation failed)
const sanitizedAttributes = attributes.map(n => validateAndSanitize(n, req.body[n]))
// True if some of the attributes new values are false -> validation failed
const someInvalid = sanitizedAttributes.some(r => !r)
if (someInvalid) {
// Throw a 422 with a neat error message if validation failed
return res.status(422).json({ 'error': 'Ugh.. That looks unprocessable!' })
}
sendMail(...sanitizedAttributes)
res.status(200).json({ 'message': 'OH YEAH' })
})
module.exports = {
path: '/api/contact',
handler: app
}
const validateAndSanitize = (key, value) => {
const rejectFunctions = {
name: v => v.length < 4,
email: v => !validator.isEmail(v),
message: v => v.length < 25
}
// If object has key and function returns false, return sanitized input. Else, return false
return rejectFunctions.hasOwnProperty(key) && !rejectFunctions[key](value) && xssFilters.inHTMLData(value)
}
const sendMail = (name, email, message) => {
console.info('Info: 🙂'
+'\n Contact page submission.'
+'\n Date: '+ new Date().toLocaleTimeString() +' '+ new Date().toLocaleDateString()
+'\n Name: '+ name
+'\n Email: '+ email
+'\n Message: '
+'\n'
+'\n'+ message
+'\n'
+'\n End of submission.'
+'\n');
let transporter = nodemailer.createTransport({
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail'
})
transporter.sendMail({
from: email,
to: 'mail#foobar.com',
subject: 'Foo Bar: Contact Form',
text: message
}, (err, info) => {
console.log(info.envelope);
console.log(info.messageId);
})
}
I recently published a Nuxt.js module that allows to send emails via a route and an injected variable. You can use it like this:
$ npm install nuxt-mail #nuxtjs/axios
In your nuxt.config.js file:
export default {
modules: [
'#nuxtjs/axios',
['nuxt-mail', {
smtp: {
host: "smtp.example.com",
port: 587,
},
}],
],
}
Use it in your component:
this.$mail.send({
from: 'John Doe',
subject: 'Incredible',
text: 'This is an incredible test message',
to: 'johndoe#gmail.com',
})
Yours doesn't work because you are not calling the correct path, in your serverMiddleware you said that /api/contact points to the contact.js file.
This means that /api/contact equals at the routes of that contact.js, thus, you should call:
this.$axios.post('/api/contact')
#manniL kindly pointed out to me on a call that in my nuxt.config.js I had set up the environment to load on 0.0.0.0 so that I could access it from a different machine.
export default {
server: {
port: 3000,
host: '0.0.0.0' // default: localhost
},
...
}
I needed to also setup the axios config with the same IP address, which is why it could not find the api/contact.js.
I have since learnt that the default scaffold for Nuxt2, when including axios, is it adds this to the nuxt.config.js to cover changes in IP:
export default {
...
axios: {
baseURL: '/'
},
...
}
Apologies, I should have posted my full nuxt.config.js in the question as it was different from a default setup.
Related
Hi I am a beginner to Sanity and I am working on a personal project. All I want to know is how do I add a user along with his profile Image selected as a file from his device to the Sanity Database. I want the details whatever they entered in this form to be saved in the backend. I am using Next.js.
<div className="flex h-screen flex-col items-center justify-center bg-[#171821]">
<div className="m-6">
<form>
<input
aria-label="Enter your email address"
type="text"
placeholder="User Name"
className="text-gray-base mr-3 mb-2 h-2 w-full roundedpy-5 px-4 text-sm bg-black text-white"/>
<button type="submit" className="mt-5 w-full bg-green-400 p-2">
Add User
</button>
</form>
</div>
</div>
Here is my simple sanity schema
export default {
name: 'user',
title: 'User',
type: 'document',
fields: [
{
name: 'user_name',
title: 'User Name',
type: 'string',
},
{
name: 'profile_image',
title: 'Profile Image',
type: 'image',
},
]
}
You can use the JavaScript client to perform mutations like this. If each user is aligned with a document, you might look at the create() method. Be sure you consider the security of your token, as you'll need to use a write token and that will provide full CRUD access to your entire project's content.
Initialize the sanity client:
import SanityClient from '#sanity/client'
export const client = sanityClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: 'v1',
token: process.env.SANITY_TOKEN,
useCdn: false,
})
write an api function in pages/api/createUser
import { client } from '../../lib/sanity'
const createUserOnSanity = async (req, res) => {
try {
const userDoc = {
_type: 'users',
name: req.body.name,
profileImage: req.body.profileImage,
...
}
await client.createIfNotExists(userDoc)
res.status(200).send({ message: 'success' })
} catch (error) {
res.status(500).send({ message: 'error', data: error.message })
}
}
export default createUserOnSanity
Then in your component, make a post request to this api
try {
await fetch(`/api/createUser`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
// uploading file from computer needs extra work
// I just pass url
profileImage: event.target.url.value,
}),
})
} catch (error) {
console.error(error)
}
I have set up Email.js to make a contact page for a website built with Next.js. It works completely fine when run locally, but does not work when hosted. The form does not even reset when the submit button is clicked. I do this in the sendEmail function. The error handler does not trigger either in the .then block. I get this error in the browser console:
Uncaught The user ID is required. Visit https://dashboard.emailjs.com/admin/integration
Here is how I send the emails:
export default function Book(props) {
const form = useRef();
const [sentMessage, setSentMessage] = useState();
const sendEmail = (e) => {
e.preventDefault();
emailjs
.sendForm(
props.SERVICE_ID,
props.EMAIL_TEMPLATE_ID,
form.current,
props.USER_ID
)
.then(
function (response) {
setSentMessage("Message sent successfully!");
},
function (error) {
setSentMessage("Message failed please email directly.");
}
);
document.getElementById("form").reset();
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<form
className={styles.form}
ref={form}
onSubmit={sendEmail}
id="form"
>
<h3>Name (required):</h3>
<input type="text" required={true} name="user_name"></input>
<h3>Email (required):</h3>
<input type="email" required={true} name="user_email"></input>
<h3>Phone number (required):</h3>
<input type="number" required={true} name="phone_number"></input>
<h3>Message (optional):</h3>
<textarea name="message"></textarea>
<button type="submit" value="Send">
Submit
</button>
{sentMessage ? <p>{sentMessage}</p> : <p></p>}
</form>
</div>
</div>
);
}
export async function getServerSideProps() {
return {
props: {
USER_ID: process.env.USER_ID,
EMAIL_TEMPLATE_ID: process.env.EMAIL_TEMPLATE_ID,
SERVICE_ID: process.env.SERVICE_ID,
},
};
}
I have a .env.local file with the template id, user id and service id that all work fine locally. I use next-env and dotenv-load in the next.config.js file like so:
dotenvLoad();
const withNextEnv = nextEnv();
module.exports = withNextEnv({
reactStrictMode: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ["#svgr/webpack"],
});
return config;
},
});
I saw some problems online that people had with Gmail and remote email servers, so I switched the account to have no 2 factor authentication and use less secure apps as well. That had no effect.
All you need to do is set up the environment variables in the next.js dashboard then rebuild the site so they take effect.
I need to do a new api in order to send an email with sendgrid. I followed the official doc and other examples so I did:
config/plugins
module.exports = ({ env }) => ({
email: {
provider: 'sendgrid',
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: 'juliasedefdjian#strapi.io',
defaultReplyTo: 'juliasedefdjian#strapi.io',
},
},
});
then I did a new folder named email in api folder
api/email/config/routes.json
{
"routes": [
{
"method": "POST",
"path": "/email",
"handler": "email.index",
"config": {
"policies": []
}
}
]
}
finally under api/email/controllers/email.js
const { default: createStrapi } = require('strapi');
module.exports = {
index: async (ctx) => {
//build email with data from ctx.request.body
await createStrapi.plugins['email'].services.email.send({
to: 'email#email.com',
from: 'email#email.com',
replyTo: 'email#email.com',
subject: 'test',
text: 'test',
});
ctx.send('Email sent!');
},
};
The real problem is that /email api returns me a 403 even if I did this from the dashboard:
I have done many APIs with strapi but I have never sent emails with it.
Is there a way to add permissions from the code? I have to say that if I use GET method it works, but I need to do it with a POST method, which doesn't. Did I miss something?
I am trying to add authentication to my Next.js project with Next-Auth, however I am stuck on a 500 internal server error after submitting credentials (http://localhost:3000/api/auth/error?error=Configuration).
I think it might be something to do with running on http://localhost:3000 but I'm not sure. Can anyone see what i'm doing wrong?
pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
const options = {
site: 'http://localhost:3000',
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
consol.log('credentials', credentials);
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' };
if (user) {
return Promise.resolve(user);
} else {
return Promise.resolve(null);
}
},
}),
],
database: process.env.MONGO_URI,
};
export default (req, res) => NextAuth(req, res, options);
pages/index.js
import { useSession } from 'next-auth/client';
export default function Home() {
const [session, loading] = useSession();
console.log('session', session);
return (
<div className="container">
<main>
{session && <p>Signed in as {session.user.email}</p>}
{!session && (
<p>
Sign in
</p>
)}
</main>
</div>
);
}
pages/_app.js
import { Provider } from 'next-auth/client';
import '../styles.css';
export default ({ Component, pageProps }) => {
const { session } = pageProps;
return (
<Provider options={{ site: process.env.SITE }} session={session}>
<Component {...pageProps} />
</Provider>
);
};
next.config.js
module.exports = {
env: {
MONGO_URI: '...',
SITE: 'http://localhost:3000',
},
};
Any help would be really appreciated.
In your pages/api/auth/[...nextauth].js add:
secret:process.env.SECRET
SECRET must be any string value like this:
SECRET:LlKq6ZtYbr+hTC073mAmAh9/h2HwMfsFo4hrfCx6gts=
Also add it in your vercel env.
Thats it, your problem will be solved.
As mentioned by #Dinesh, my problem was solved by adding a NEXTAUTH_SECRET to both Vercel Environment Variables and to [...nextauth].ts
Local deployment worked without that variable, but vercel deployment needed the secret. In addition to adding the Vercel environment variable, my working [...nextauth].ts looks like:
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
async jwt({ token }) {
token.userRole = "user"
return token
},
},
})
You had a typo under your asyc function consol.log('credentials', credentials);
Fix to console.log('credentials', credentials); and that should solve the problem.
I faced a similar problem and it was because I didn't add any providers array.
In the future, fix any typos or errors within your [...nextauth].js to fix 500 error
Keep in mind NEXTAUTH_URL to be set for yourwebsite.com
process.env.NEXTAUTH_URL=https://yourwebsite.com
I'm trying to create a middleware for check role of my users.
// middleware/is-admin.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'admin' ) {
return context.redirect('/errors/403')
}
}
In my .vue file, I'm putting this on:
middleware: [ 'is-admin' ]
It works.
Now, I'd like to check if the user also has another role. So, I create a new middleware:
// middleware/is-consultant.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'consultant' ) {
return context.redirect('/errors/403')
}
}
And in my .vue file:
middleware: [ 'is-admin', 'is-consultant' ]
Unfortunately, when I do that, if I visit the route with an administrator role, it does not work anymore.
Can you tell me how I can create a middleware that checks multiple roles with Nuxt.js?
Thank you!
The idea is that every page has its authority level. Then in middleware you can compare your current user authority level with the current page authority level, and if it's lower redirect the user. It's very elegant solution that was proposed by Nuxt.js creator. GitHub issue.
<template>
<h1>Only an admin can see this page</h1>
</template>
<script>
export default {
middleware: 'auth',
meta: {
auth: { authority: 2 }
}
}
</script>
Then in your middleware/auth.js:
export default ({ store, route, redirect }) => {
// Check if user is connected first
if (!store.getters['user/user'].isAuthenticated) return redirect('/login')
// Get authorizations for matched routes (with children routes too)
const authorizationLevels = route.meta.map((meta) => {
if (meta.auth && typeof meta.auth.authority !== 'undefined')
return meta.auth.authority
return 0
})
// Get highest authorization level
const highestAuthority = Math.max.apply(null, authorizationLevels)
if (store.getters['user/user'].details.general.authority < highestAuthority) {
return error({
statusCode: 401,
message: 'Du måste vara admin för att besöka denna sidan.'
})
}
}
You can use this feature in Nuxt
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
The scope can be anything you want e.g Consultant, Editor etc.
Check the documentation
Updated
Since you are using Laravel
You can have a role column in your user table
e.g
$table->enum('role', ['subscriber', 'admin', 'editor', 'consultant', 'writer'])->default('subscriber');
Then create a API resource, check the documentation for more
To create a user resource, run this artisan
php artisan make:resource UserResource
Then in your resource, you can have something like this
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'gender' => $this->gender,
'country' => $this->country,
'avatar' => $this->avatar,
'role' => $this->role,
];
}
Then you can import it to your controller like this
use App\Http\Resources\UserResource;
You can get the resource like this
$userdata = new UserResource(User::find(auth()->user()->id));
return response()->json(array(
'user' => $userdata,
));
In Nuxt
To do authentication in Nuxt
Install nuxt auth and axios
Using YARN : yarn add #nuxtjs/auth #nuxtjs/axios
Or using NPM: npm install #nuxtjs/auth #nuxtjs/axios
Then register them in your nuxtconfig.js
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
],
In your nuxtconfig.js, add this also
axios: {
baseURL: 'http://127.0.0.1:8000/api'
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/login', method: 'post', propertyName: 'access_token' },
logout: { url: '/logout', method: 'post' },
user: { url: '/user', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: 'Bearer',
globalToken: true
// autoFetchUser: true
}
}
}
The URL been the endpoints
Check Documentation for more
To restrict certain pages in Nuxt to Specific User.
> Create a middlweare e.g isadmin.js
Then add this
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
Then go to the Page, add the middleware
export default {
middleware: 'isadmin'