Destroying a session in koa-session - koa

So I have a simple authorization system where on login user is saved to a session. Problem is in situation like that, where I want to delete a session, thus logging the user out, session carries onto the next request.
app.get('/logout', ctx => {
ctx.session = null;
ctx.redirect('/');
});
So in this situation code below will render user info on the page after redirecting from logout:
app.get('/', ctx => ctx.body = ctx.session);
Cookies aren't cleared too.

I am fairly new to koa-session but was able to implement user authentication in a project a while back, by setting the user's id object to ctx.session.id.
Login
router.post('/login', async ctx => {
const userDetails = ctx.request.body
try {
const userDetails = ctx.request.body
const userId = await user.login(userDetails)
ctx.session.authenticated = true
ctx.session.id = userId
ctx.redirect('/')
} catch (error) {
await ctx.render('login', {error: error})
} finally {
await user.tearDown()
}
})
Logout
router.get('/logout', async ctx => {
if (ctx.session.authenticated === true) {
ctx.session.authenticated = false
ctx.session.id = undefined
}
ctx.redirect('/')
})
In your case, you may want to assign ctx.session.user = userObject, then when logging a user out, reassign to ctx.session.user = null.

Related

Nextjs getServerSideProps does not pass session to component

Good morning. I am using NextJS and get the session in the getServerSideProps block.
I am passing multiple parameters to the return object, however, the session does not pass. All of the other key value pairs works, but when I try to log the session, It comes undefined... I don't understand...
const DashboardPage = ({ session, secret }) => {
const [loading, setloading] = useState(false);
//This does not work
console.log('Session: ', session);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={session?.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
// get sessions with added user Id in the session object
const session = await requireAuthentication(context);
// This works
console.log(session);
if (!session) {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
else {
return {
props: {
session: session,
secret: 'Pasarika este dulce'
}
}
}
}
export default DashboardPage;
The purpose of the requireAuthentication function is to create a new session object where I will insert another key value pair that will contain the user id and the session that will be used in the entire app.
In that function I get the user by email that is returned by the session and get the Id from the db. I than return the new session object that looks like this:
{
user: {
name: 'daniel sas',
email: 'email#gmail.com',
image: 'https://lh3.googldeusercontent.com/a/A6r44ZwMyONqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'//This is the required thing in my app
},
expires: '2022-12-23T08:04:08.263Z'
}
The following function is used to get the data from the database
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);
// If there is no user or there is an error ret to signup page
if (!session) return null
// 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 null;
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
return newSession;
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}

How do I use createAsyncThunk to repeatedly fetch data automatically?

The goal is to repeatedly fetch generation data when the Id changes...The generation Id changes at the backend
Only AFTER I used redux tool kit and createAsyncThunk, did it stop refreshing generation Id automatically, unless I manually refresh the page (will it show the latest generationId)
The change I've made, so how should I do it? Thanks.
in generationSlice.js
export const getGeneration = createAsyncThunk(
'generation/getGeneration',
async () => {
try {
const resp = await axios(url)
return resp.data
} catch (error) {
return error.response
}
}
)
and
in components/Generation.js
const { generationId, expiration, isLoading } = useSelector((store) => {
return store.generation
})
const dispatch = useDispatch()
useEffect(() => {
dispatch(getGeneration())
}, [generationId])

How to mutate user's session in nextauth when you change user data?

I want to update the user's data but after updating the user's data how to make also the change appear in session?
[...nextauth].js
callbacks: {
jwt: ({ token, user }) => {
if (user) {
token.id = user.id;
token.name = user.name;
token.surname = user.surname;
token.email = user.email;
token.role = user.role;
}
// Here, check the token validity date
if (token.tokenExpiration < Date.now()) {
// Call the endpoint where you handle the token refresh for a user
const user = axios.post(
`${process.env.API_URL}/auth/authentication/refresh`,
{
refreshToken: token.refreshToken,
}
);
// Check for the result and update the data accordingly
return { ...token, ...user };
}
return token;
},
session: ({ session, token }) => {
if (token) {
session.id = token.id;
session.name = token.name;
session.surname = token.surname;
session.email = token.email;
session.role = token.role;
}
return session;
},
},
secret: process.env.SECRET_KEY,
jwt: {
secret: process.env.SECRET_KEY,
encryption: true,
maxAge: 5 * 60 * 1000,
},
api/user/index.js
Here I update the user content, what should I do to update the session object detail
const updateUser = await prisma.user.update({
where: {
email: 'test#email.io',
},
data: {
name: 'User',
},
})
session object
name : Company
email : test#email.io
expires : 2022-04-26T18:44:36.424Z
id : 2
name : Company
surname : Surname
email : test#email.io
role : 2
I have the same problem and have a hacky workaround. In the session callback, get the user from the database. This callback is triggered whenever the session is checked (docs), so you can call getSession(), useSession(), or even signIn() somewhere after you update the user.
async function getUserFromDB(accessToken) {
// I'm not super familiar with prisma but you get the idea
const user = await prisma.user.findUnique({
// logic to get user
// maybe it needs your accessToken
});
return user;
}
// [...nextAuth].js
session: ({ session, token }) => {
if (token) {
session = await getUserFromDB(token.accessToken);
}
return session;
}
Obvious drawback: There is a call to get the user from the database anytime the session is checked.

How to set Cognito Groups in Migration trigger

I am currently building a migration solution from an AWS Userpool to another using the CognitoTrigger "User Migration".
I have a Group I want to set during migration but I cannot do it because the user isn't created before the whole context finishes.
How can I solve this? I don't want to create a PostAuth - lambda because I only need/want/can run this once per migration and I also want to do this the instant (or up to a few minutes later) the migration happens. (or is it possible to make this PostAuth check if it is the first time it triggers?)
I tried PostConfirm in the hopes of this triggering when the user was created but that did not trigger.
If someone else runs into this - I solved this using a combination of a User Migration trigger and a Pre Token Generation trigger.
In the User Migration trigger (mostly copied from https://github.com/Collaborne/migrate-cognito-user-pool-lambda) look up and create the user if auth fails/user doesn't exist in the new pool.
In the Pre Token Generation trigger if the user hasn't been added to groups yet look up group membership in the old user pool (adminListGroupsForUser), add them to the new pool (adminAddUserToGroup). The crucial part is to override the group membership claims in the response so that they will be added to the token on the client side (groupsToOverride is just an array of the group names the user is part of):
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
},
"groupOverrideDetails": {
"groupsToOverride": groupsToOverride,
}
}
};
Thank you #BrokenGlass, I used this approach. For anyone else here's an example Typescript preTokenGeneration lambda.
//preTokenGenerations.ts
import { PreTokenGenerationTriggerHandler } from 'aws-lambda';
import { preTokenAuthentication } from '../services/preTokenService';
export const handler: PreTokenGenerationTriggerHandler = async (event, context) => {
console.log({
event,
context,
request: event.request,
userAttributes: event.request.userAttributes,
clientMetadata: event.request.clientMetadata,
groupConfiguration: event.request.groupConfiguration,
})
const OLD_USER_POOL_ID = process.env.OLD_USER_POOL_ID;
if (!OLD_USER_POOL_ID) {
throw new Error("OLD_USER_POOL_ID is required for the lambda to work.")
}
const {
userPoolId,
request: {
userAttributes: {
email
}
},
region
} = event;
switch (event.triggerSource) {
case "TokenGeneration_Authentication":
const groupsToOverride = await preTokenAuthentication({
userPoolId,
oldUserPoolId: OLD_USER_POOL_ID,
username: email,
region
})
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
},
"groupOverrideDetails": {
"groupsToOverride": groupsToOverride,
}
}
};
return event
default:
console.log(`Bad triggerSource ${event.triggerSource}`);
return new Promise((resolve) => {
resolve(event)
});
}
}
// preTokenService.ts
import { getUsersGroups, cognitoIdentityServiceProvider, assignUserToGroup } from "./cognito"
interface IPreTokenAuthentication {
userPoolId: string;
oldUserPoolId: string;
username: string;
region: string
}
export const preTokenAuthentication = async ({ userPoolId, oldUserPoolId, username, region }: IPreTokenAuthentication): string[] => {
const cognitoISP = cognitoIdentityServiceProvider({ region });
const newPoolUsersGroups = await getUsersGroups({
cognitoISP,
userPoolId,
username
});
// If the user in the new pool already has groups assigned then exit
if (newPoolUsersGroups.length !== 0) {
console.log("No action required user already exists in a group")
return;
}
const oldPoolUsersGroups = await getUsersGroups({
cognitoISP,
userPoolId: oldUserPoolId,
username
});
// If the user in the old pool doesn't have any groups then nothing else for this function to do so exit.
if (oldPoolUsersGroups.length === 0) {
console.error("No action required user migrated user didn't belong to a group")
return;
}
console.log({ oldPoolUsersGroups, newPoolUsersGroups })
await assignUserToGroup({
cognitoISP,
userPoolId,
username,
groups: oldPoolUsersGroups
})
return oldPoolUsersGroups;
}
// cognito.ts
import { AdminAddUserToGroupRequest, AdminListGroupsForUserRequest } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { CognitoIdentityServiceProvider } from 'aws-sdk';
interface ICognitoIdentityServiceProvider {
region: string;
}
export const cognitoIdentityServiceProvider = ({ region }: ICognitoIdentityServiceProvider) => {
const options: CognitoIdentityServiceProvider.Types.ClientConfiguration = {
region,
};
const cognitoIdentityServiceProvider = new CognitoIdentityServiceProvider(options);
return cognitoIdentityServiceProvider;
}
interface IGetUsersGroups {
cognitoISP: CognitoIdentityServiceProvider,
userPoolId: string,
username: string
}
export const getUsersGroups = async ({ cognitoISP, userPoolId, username }: IGetUsersGroups): Promise<string[]> => {
try {
const params: AdminListGroupsForUserRequest = {
UserPoolId: userPoolId,
Username: username,
}
const response = await cognitoISP.adminListGroupsForUser(params).promise();
return response.Groups?.map(group => group.GroupName!) || [];
} catch (err) {
console.error(err)
return [];
}
}
interface IAssignUserToGroup {
cognitoISP: CognitoIdentityServiceProvider,
username: string;
groups: string[];
userPoolId: string;
}
/**
* Use Administration to assign a user to groups
* #param {
* cognitoISP the cognito identity service provider to perform the action on
* userPoolId the userPool for which the user is being modified within
* username the username or email for which the action is to be performed
* groups the groups to assign the user too
* }
*/
export const assignUserToGroup = async ({ cognitoISP, userPoolId, username, groups }: IAssignUserToGroup) => {
console.log({ userPoolId, username, groups })
for (const group of groups) {
const params: AdminAddUserToGroupRequest = {
UserPoolId: userPoolId,
Username: username,
GroupName: group
};
try {
const response = await cognitoISP.adminAddUserToGroup(params).promise();
console.log({ response })
} catch (err) {
console.error(err)
}
}
}
Tips, make sure under the trigger section in Cognito that you have the migration and preToken triggers set. You also need to ensure SRP is not enabled so the lambda can see the password to be able to successfully migrate the user.
Things to test is that when the user is first migrated that they are assigned their groups. And for future logins they are also assigned to their groups.
Let me know if anyone has any feedback or questions, happy to help.

Store session in operation hook - Loopback

I want to store some data other than userId or accessToken to store in a session, in after save or before save operation hook in Loopback application using express-session.
I have this in my server/server.js :
....
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
....
app.use(session({
name:'session-name',
secret: 'keyboard cat',
store: new MongoStore({url: 'mongodb://localhost/test', ttl:1}),
resave: false,
saveUninitialized: true
}));
And as I'm defining the remote-method with some parameters it actually passing the parameter and not the req object, so I can't do it the express way.
How can I use the session to store and get value?
EDIT :
I have found a way to set the session in remote method, by adding this to my model.json's remote-method :
"accepts": [
{
"arg": "req",
"type": "object",
"http": {
"source": "req"
}
}
]
And, adding the req parameter to the remote-method function,
Model.remoteMethod = function (req, callback) {
req.session.data = { 'foo': 'bar' }
callback(null)
};
Now, the issue is I want to get this session value in operation hook
Model.observe('before save', function (ctx, next) {
//How to get the session here?
})
try this now :
you can set ctx value :
var LoopBackContext = require('loopback-context');
MyModel.myMethod = function(cb) {
var ctx = LoopBackContext.getCurrentContext();
// Get the current access token
var accessToken = ctx && ctx.get('accessToken');
ctx.set('xx', { x: 'xxxx' } );
}
it's get ctx value :
module.exports = function(MyModel) {
MyModel.observe('access', function(ctx, next) {
const token = ctx.options && ctx.options.accessToken;
const userId = token && token.userId;
const modelName = ctx.Model.modelName;
const scope = ctx.where ? JSON.stringify(ctx.where) : '<all records>';
console.log('%s: %s accessed %s:%s', new Date(), user, modelName, scope);
next();
});
};
loopback context store userId and accesTokan. in whole web you can access using ctx it's work like session in loopback.

Resources