I have the following handler, and I want to remove all cookies. I tried to return a Set-Cookie header, but that doesn't work. How can I remove all cookie/a specific cookie correctly?
export const handle: Handle = async ({ event, resolve }) => {
const cookies = parse(event.request.headers.get('cookie') || '');
const sessionTokenKey = Object.keys(cookies).find(key => key.includes('abc'))
const sessionToken = cookies[sessionTokenKey]
const response = await resolve(event);
response.headers.set('Set-Cookie', "")
return response
};
Something like this should work:
const response = await resolve(event);
response.headers.set(
'set-cookie',
serialize('cookie name', '', {
expires: Date.now() - 3600
})
)
because of type mismatch, this worked for me:
import * as cookie from 'cookie'
response.headers.set(
'set-cookie': cookie.serialize('token', '', {
expires: new Date("Thu, 01 Jan 1970 00:00:01 GMT")
})
)
Related
I have a bunch of static promotional pages. However, if the promo has passed (expired), i need to navigate/show an expired page. What is the correct way to do this with static pages in NextJS?
Attempt 1: do a check if expired in getStaticProps. Issue, revalidation happens every 600 seconds. So this could happen at 12:28am instead of 12:00am on the dot (depending on when i deployed it).
So it isn't showing an expired page on time. How to fix this issue? Or implement the 'proper' way to change out the page.
export const getStaticPaths = async () => {
const pageSlugs = await api.getAllSlugs();
const paths = pageSlugs.map((slug) => (params: {promo: slug})
);
return {
paths,
fallback: "blocking"
};
};
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await api.getSlug(params.promo, {
preview,
enviroment: previewData?.enviroment
});
const isExpired = checkIfExpired(page.promoStart, page.promoEnd);
const expiredPage =
isExpired
? await api.getPage("expired-page", {
preview,
enviroment: previewData?.enviroment
})
: null;
return {
props: {
page,
isExpired,
expiredPage,
},
revalidate: 600
};
}
You could calculate the revalidate time dynamically:
export async function getStaticProps({ preview = false, params, previewData }) {
const page = await api.getSlug(params.promo, {
preview,
enviroment: previewData?.enviroment
});
const isExpired = checkIfExpired(page.promoStart, page.promoEnd);
const expiredPage =
isExpired
? await api.getPage("expired-page", {
preview,
enviroment: previewData?.enviroment
})
: null;
let revalidate = 600
if (Date.now() <= page.promoStart) {
revalidate = (page.promoStart - Date.now()) / 1000
} else if (date.now() > page.promoStart && Date.now() <= page.promoEnd) {
revalidate = (page.promoEnd - Date.now()) / 1000
}
return {
props: {
page,
isExpired,
expiredPage,
},
revalidate
};
}
This assumes promoEnd and promoStart are date objects but you can adjust as needed. Also make sure server time aligns with the timezone used on the date object.
Below is how i create the client.
import { create as ipfsHttpClient } from 'ipfs-http-client';
const projectId = 'xx';
const projectSecret = 'xx';
const auth = `Basic ${Buffer.from(`${projectId}:${projectSecret}`).toString('base64')}`;
const options = {
host: 'ipfs.infura.io',
protocol: 'https',
port: 5001,
apiPath: '/ipfs/api/v0',
headers: {
authorization: auth,
},
};
const dedicatedEndPoint = 'https://xx.infura-ipfs.io';
const client = ipfsHttpClient(options);
Here is the function that will be called from front-end that takes in a file, uploads to IPFS and returns URL. Please note that the "ipfsHTTPClient()" is just the create function.
const uploadToIPFS = async (file) => {
try {
const added = await client.add({ content: file });
const url = `${dedicatedEndPoint}${added.path}`;
return url;
} catch (error) {
console.log('Error uploading file to IPFS: ', error);
}
};
The error I am getting is
POST https://ipfs.infura.io:5001/ipfs/api/v0/add?stream-channels=true&progress=false 403 (Forbidden)
When i console log the error it says the IPFS method is not supported.
On the IPFS forum, i have seen someone say that add function does not work anymore but i have also seen people using it and it working. Im not sure whats wrong here.
Here is how i call the function on front-end
const { uploadToIPFS } = useContext(NFTContext);
// function called from useDropzone
const onDrop = useCallback(async (acceptedFile) => {
const url = await uploadToIPFS(acceptedFile[0]);
setFileUrl(url);
}, []);
All the above code is correct and the error was from Next.js
Needed to add
images: {
domains: ['xx.infura-ipfs.io'],
},
to the next.config.js file.
I have resolved this problem
so make sure first you have installed buffer
npm install --save buffer
then import it in your file
import {Buffer} from 'buffer';
then it works successfully
import { create } from "ipfs-http-client";
import { Buffer } from "buffer";
const projectId = "YOUR_INFURA_PROJECT_ID";
const projectSecret = "YOUR_INFURA_PROJECT_SECRET";
const auth = `Basic ${Buffer.from(`${projectId}:${projectSecret}`).toString(
"base64"
)}`;
const client = create({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
apiPath: "/api/v0",
headers: {
authorization: auth,
},
});
const uploadFiles = async (e) => {
e.preventDefault();
setUploading(true);
if (text !== "") {
try {
const added = await client.add(text);
setDescriptionUrl(added.path);
} catch (error) {
toast.warn("error to uploading text");
}
}
I am stuck on this problem for 2 days. I am sending POSTrequest from frontend to the backend (and other GET requests too but the problem is only with POST). However, when my data goes to the backend it does not post anything to the rest api even though response is 200 OK. That's why when in response it should have given the posted data, it can't find it and gives null. This is my POST code in backend index.js:
const { response, request } = require('express');
require('dotenv').config()
const express = require('express');
const morgan = require('morgan');
const Contact = require('./models/contact.cjs');
const cors = require('cors')
const app = express();
app.use(express.json())
app.use(express.static('build'))
app.use(cors())
morgan.token('body', req => {
return JSON.stringify(req.body)
})
app.use(morgan(':method :url :status :res[content-length] - :response-time ms :body'));
const generateId = () => {
const randNum = Math.floor(Math.random() * 5000)
return randNum;
}
app.post('/api/persons', (req, res) => {
const body = req.body
console.log(body)
if (!body.name || !body.number) {
return res.status(400).json({
error: "missing data"
})
} else if (Contact.find({name: body.name})) {
Contact.findOneAndUpdate({name: body.name}, {$set: {number: body.number}}, {new:true})
.then(updatedContacts =>
res.json(updatedContacts)
)
.catch(err => console.log(err))
} else {
const contact = Contact({
id: generateId(),
name: body.name,
number: body.number,
date: new Date()
})
contact.save()
.then(savedContact => {
console.log(savedContact)
res.json(savedContact)
})
.catch(err => {
console.log(err)
})
}
})
const PORT = process.env.PORT
app.listen(PORT, () => {
console.log(`Server is working on ${PORT}`)
})
and this is how my frontend sends data to backend: contacts.js:
const create = (newObject) => {
const readyToPost = {
method: 'post',
url: `${baseUrl}`,
data: newObject,
headers: {'Content-Type': 'application/json'},
json: true
}
const request = axios(readyToPost)
return request.then(response => {
console.log(response.data)
return response.data
})
.catch(err => {
console.log(err)
})
}
And this is my react app's frontend.
Any ideas about why my data becomes null?
Any help would be appreciated!
Due to the synchronous nature of your code, the condition Contact.find({name: body.name}) was always returning the Query object which is true due to which the else if block was getting executed even when there was no such document. After entering the else if block, since there was no match, so findOneAndUpdate() was returning null.
Use findOne() instead of find(). find() returns a cursor which is empty but true whereas findOne() returns the first document matched (if matched) or else it will return null (if not matched).
// index.js (Backend)
app.post("/api/persons", async (req, res) => {
const body = req.body;
if (!body.name || !body.number) {
return res.status(400).json({
error: "missing data",
});
}
// Using findOne() instead of find(). Returns null if record not found.
const existing = await Contact.findOne({ name: body.name });
if (existing) {
Contact.findOneAndUpdate(
{ name: body.name },
{ $set: { number: body.number } },
{ new: true }
)
.then((updatedContacts) => {
console.log(updatedContacts);
res.status(200).json(updatedContacts);
})
.catch((err) => console.log(err));
} else {
const contact = Contact({
id: generateId(),
name: body.name,
number: body.number,
date: new Date(),
});
contact
.save()
.then((savedContact) => {
console.log(savedContact);
res.status(201).json(savedContact);
})
.catch((err) => {
console.log(err);
});
}
});
I use django rest_framework_simplejwt package to generate JWT tokens and set them in browsable cookie with Httponly flag. At the Django side it work perfectly but at react side it does not work perfectly.
I read many answers related to this question like this and this but they have not solved my problem yet.
Please help me understand where I'm wrong.
DJANGO SIDE
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from django.conf import settings
from rest_framework import status
from rest_framework_simplejwt.exceptions import TokenError,\
InvalidToken
from rest_framework.response import Response
class MyTokenObtainPairView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
# set access token in browser with Httponly cookie.
res = Response(serializer.validated_data, status=status.HTTP_200_OK)
access_token = serializer.validated_data['access']
res.set_cookie("access_token", access_token, max_age=settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME').total_seconds(),samesite='Lax',secure=False, httponly=True)
return res
authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings
class CookieHandlerJWTAuthentication(JWTAuthentication):
def authenticate(self, request):
# If cookie contains access token, put it inside authorization header
access_token = request.COOKIES.get('access_token')
if(access_token):
request.META['HTTP_AUTHORIZATION'] = '{header_type} {access_token}'.format(
header_type=settings.SIMPLE_JWT['AUTH_HEADER_TYPES'][0], access_token=access_token)
return super().authenticate(request)
urls.py
from .views import MyTokenObtainPairView
urlpatterns = [
......
path('auth/', include('djoser.urls')),
# path('auth/', include('djoser.urls.jwt')),
path('auth/api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
]
settings.py
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
'http://127.0.0.1:8000'
)
Work perfectly(Token set in cookie with Httponly.):
REACTJS SIDE
Login.js
axios.defaults.withCredentials = true
const Login = () => {
const [ivalue, setValue] = useState({});
const { state, dispatch } = useContext(Authcontext);
.......
const loginClick = (e) => {
e.preventDefault();
setLoader(true);
axios.post('http://127.0.0.1:8000/auth/api/token/', {
username: ivalue.username,
password: ivalue.password,
})
.then((res) => {
setValue({ password: "" });
dispatch({
type: LOGIN_SUCCESS,
});
setLoader(false);
history.push("/my_profile");
})
.catch((err) => {
setLoader(false);
setOpen({ act: true, msg: "error" });
dispatch({
type: LOGIN_ERROR,
});
setError(err.response.data);
});
};
.........
};
Myprofile.js
axios.defaults.withCredentials = true
const MyProfile = () => {
const history = useHistory();
const { state, dispatch } = useContext(Authcontext);
useEffect(() => {
const auth_check = () => {
axios.get('http://127.0.0.1:8000/auth/users/me/')
.then((res) => {
dispatch({
type: AFTER_LOGIN,
payload: res.data
});
})
.catch((err) => {
history.push('/login')
});
};
auth_check();
}, []);
}
.......
JWT Response(cookie not set in browser).
Unauthorized Error
I set backend and frontend under same IP. ex. my backend is
localhost:8000
and frontend is
localhost:3000
different ports same Ip.
This is not the scenario when it goes to production you can have any domain.
You are getting a samesite=Lax value in your set-cookie header try using CSRF_COOKIE_SECURE = True, SESSION_COOKIE_SECURE = True, CSRF_COOKIE_SAMESITE = 'None', SESSION_COOKIE_SAMESITE = 'None'
this will let browser to set cookies even from a different domain.
and use withCredentials: true on your client side.
I'm learning how to test a frontend webapp without any connection to the API.
My problem is: I have to test an POST HTTP Request but always get an error : TypeError: loginUser(...).then is not a function.
I know my expect is not correct. I must change the data for a JWT token, and also don't know yet hot to do it.
It's a simple user authentication. Http post sending an email and password, getting back a JWT (json web token). I have to write a test to make sure I've send the correct information and get a JWT as response.
Thanks for your help
Here is my code:
//login.test.js
const expect = require('chai').expect;
const loginUser = require('../src/actions/authActions').loginUser;
const res = require('./response/loginResponse');
const nock = require('nock');
const userData = {
email: 'test#test.com',
password: '123456'
};
describe('Post loginUser', () => {
beforeEach(() => {
nock('http://localhost:3000')
.post('/api/users/login', userData )
.reply(200, res);
});
it('Post email/pwd to get a token', () => {
return loginUser(userData)
.then(res => {
//expect an object back
expect(typeof res).to.equal('object');
//Test result of name, company and location for the response
expect(res.email).to.equal('test#test.com')
expect(res.name).to.equal('Tralala!!!')
});
});
});
//authActions.js
import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import {
GET_ERRORS,
SET_CURRENT_USER,
USER_LOADING
} from "./types";
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post("/api/users/login", userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
// loginResponse.js
module.exports = { email: 'test#test.com',
password: '123456',
name: "Tralala!!!"
};
Actual result:
1) Post loginUser
Post email/pwd to get a token:
TypeError: loginUser(...).then is not a function
at Context.then (test/login.test.js:37:12)
The way you called loginUser method is not correct. This method returns another function. So, instead of loginUser(userData), you must also specify the dispatch parameter e.g. loginUser(userData)(dispatch).then().
I changed the method to specify return before axios statement
export const loginUser = userData => dispatch => {
return axios // adding return
.post("/api/users/login", userData)
.then(res => {
...
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
for test, I may involve Sinon to spy the dispatch
it("Post email/pwd to get a token", () => {
const dispatchSpy = sinon.spy();
return loginUser(userData)(dispatchSpy).then(res => {
//expect an object back
expect(typeof res).to.equal("object");
//Test result of name, company and location for the response
expect(res.email).to.equal("test#test.com");
expect(res.name).to.equal("Tralala!!!");
});
});
Hope it helps