Decorator for getting current user NestJs + MongoDB - mongodb

I'm using NestJs + MongoDb
This custom decorator doesn't give me current user. I get the first user from my database.
I've checked this from different users and I always get the same answer.
Decorator:
export const CurrentUser = createParamDecorator((data, req): UserModel => {
const request = req.switchToHttp().getRequest();
return request.user
});
Controller
#UseGuards(AuthGuard('jwt'))
#UsePipes(new ValidationPipe())
#HttpCode(200)
#Patch(':friendId')
#Auth()
async toggleFriend(
#CurrentUser() user: UserModel,
) {
console.log('CurrentUser', user)
}

Study below section, from it you will be able to understand how information is added to the authorization token and then further extraction of this information.
https://docs.nestjs.com/security/authentication#jwt-functionality

Related

creating a sub user that inherit premium status from an admin?

I have this cloud function that I pass the id of an admin (sharedId)and id of sub user (subId), then make a quarry to get the admin's premiumUntill value of the doc, and copy it to the sub user. so if admin premium goes away.. a sub-user will become obsolete as well, the issue here is I am trying to find some safe guard the problem is anyone who can get an active premium admin id can use this function to make him self premium as well.... any idea how to go about this I don't want to use the method where each time a user logs in it checks the premium in the admin doc its very recourses consuming ?
my current solution is , I have the sub user id created already in the admin and stored in an array of sub users which is an immutable doc,
what I do is check if the incoming id in data.subid will be equal to snapshopt.subuss.id in that doc will this make sure that no body can mess with the data ? this will make the id a constant to verify the incoming data against. but I still it might have an issue.
export const createSubUser = functions.https.onCall(async (data, context) => {
const snapshot = await db.collection('AdminData').doc(data.sharedId).get();
// Safe Guard !!!!!1
// if(//some guard logic)
// Current solution
// getting the data from an immutable doc
const getSubuser = snapshot.data().subusers.filter((el: any) => el.subId === data.subId);
if (getSubuser[0].subId === data.subId) {
const payload = {
user: 'sub',
verifiedEmail: false,
subId: data.subId,
sharedId: data.sharedId,
createdAt: admin.firestore.Timestamp.now(),
premiumUntill: snapshot.data()?.premiumUntill,
};
return db
.collection('SubData')
.doc(context.auth?.uid!)
.set(payload)
.catch((err: any) => {
console.log(err);
});
});

strapi get related objects of User

I'm using strapi community edition v3.6.8. I have two different models ,User and CarModel. The User Model is strapi's integrated user model. The relation User: CarModel is 1:n
So I've got a profile page in which I want to fetch the User and their related CarModels. I can't get my head around how to achieve this.
I've read several answers that include creating a service which then fetches the related CarModelobjects but I can't figure out what to put into the service.
So the conclusion I've reached so far is that it is probably best if I just create a custom endpoint which fetches the current user and related objects.
How do I go on about this? This is the code I currently have:
axios.get(`http://localhost:1337/users/currentUser`, {
headers: {
Authorization: `Bearer ${token}`
}
})
In extensions/users-permissions/config I've created a routes.json with this content:
"method": "GET",
"path": "/users/currentUser",
"handler": "User.currentUser",
"config": {
"policies": ["policies.isAuthenticated"]
}
}
in config/policies I've created a is-authenticated.js - File with the following content:
module.exports = async (ctx, next) => {
if (ctx.state.user) {
return await next();
}
ctx.unauthorized(`You're not logged in!`);
};
And lastly in extensions/users-permissions/controllers I've created a User.js file with the following content:
const { sanitizeEntity } = require('strapi-utils');
const sanitizeUser = user =>
sanitizeEntity(user, {
model: strapi.query('user', 'users-permissions').model,
});
module.exports = {
currentUser: async (ctx, next) => {
strapi.query('user').find({id: ctx.id}, ['car-model']);
await next();
}
};
So now my questions would be:
1st: Something is wrong because when trying to GET /users/currentUser I get a 403. What exactly am I doing wrong?
2nd: Is this approach even valid in the first place?
And 3rd: What would be the correct approach to solving this problem? Because somewhere else I've read another approach which included writing a custom service which handles resolving the relation, but this looked very complicated imho, considering I'm simply trying to resolve a relation that already exists in the database.
I've also tried manipulating the users/me endpoint which didn't yield any results (and is probably also discouraged).
Interestingly: when the user logs in, I get the user object and all foreign key relations returned. Only when I query /users/me I get only the user data without relations. So I've read that this is a security feature, but what endpoint is used then, when posting to /auth/local and why does this endpoint return the user and related objects?
Could I use this endpoint instead of /users/me?
Any help to this problem would be greatly appreciated, best regards,
deM
So for anyone else looking for a solution, I figured it out. I added a custom route to currentUser as described above then I added a controller for this route in which I put the following code:
currentUser: async (ctx, next) => {
let carModelsOfUser = await (strapi.query('user', 'users-permissions').findOne({id: ctx.state.user.id}, ['carModels', 'carModels.images', 'carModels.ratings.rating']));
return carModelsOfUser;
}
CAUTION!
This also returns the user's hashed password and other potentially sensitive information.
Strapi offers the sanitizeEntity function to remove sensitive information, but as of now I haven't figured out how to use this in that context, as I'm not using the "raw" user here but instead joining some fields.

Setting custom claims for Firebase auth from flutter

I'm using Firebase auth for an app, but as part of user creation I need to set some custom claims.
I've written a cloud function to set the claims when a user is created:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(user => {
let customClaims;
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
})
.then(() => {
// Update real-time database to notify client to force refresh.
const metadataRef = admin.database().ref("metadata/" + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
return metadataRef.set({
refreshTime: new Date().getTime()
});
})
.then(() => {
return admin.auth().getUser(user.uid);
})
.then(userRecord => {
console.log(userRecord);
return userRecord.toJSON();
})
.catch(error => {
console.log(error);
});
});
When I print out to the console the userRecord I can see the custom claims are set correctly.
Then in flutter I get the token from the created user, but it then doesn't seem to have the custom claims attached.
I'm using this code to create the user and print the claims in flutter
Future<FirebaseUser> signUp({String email, String password}) async {
final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
email: email,
password: password,
)).user;
IdTokenResult result = await (user.getIdToken(refresh: true));
print('claims : ${result.claims}');
return user;
}
If I inspect the token itself in a jwt debugger I can see its not got the custom claims on it.
Is it that I need some additional steps to try and get an updated token once the claims have been set?
I've tried user.reload() and user.getIdToken(refresh: true) but they don't seem to help.
Any ideas on how to get the token that has the custom claims?
For future reference, I managed to get this working with Doug's suggestions.
Here's my firebase sdk admin function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestore = admin.firestore();
const settings = {timestampsInSnapshots: true};
firestore.settings(settings);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(async user => {
// Check if user meets role criteria:
// Your custom logic here: to decide what roles and other `x-hasura-*` should the user get
let customClaims;
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
})
.then(async () => {
await firestore.collection('users').doc(user.uid).set({
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
})
.catch(error => {
console.log(error);
});
});
Then on the flutter side of things
Future<FirebaseUser> signUp({String email, String password}) async {
final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
email: email,
password: password,
)).user;
currentUser = user;
await waitForCustomClaims();
return user;
}
Future waitForCustomClaims() async {
DocumentReference userDocRef =
Firestore.instance.collection('users').document(currentUser.uid);
Stream<DocumentSnapshot> docs = userDocRef.snapshots(includeMetadataChanges: false);
DocumentSnapshot data = await docs.firstWhere((DocumentSnapshot snapshot) => snapshot?.data !=null && snapshot.data.containsKey('createdAt'));
print('data ${data.toString()}');
IdTokenResult idTokenResult = await (currentUser.getIdToken(refresh: true));
print('claims : ${idTokenResult.claims}');
}
Hopefully this will help somebody else looking to do similar.
The code you're showing is likely trying to get custom claims too soon after the account is created. It will take a few seconds for the function to trigger after you call auth.createUserWithEmailAndPassword. It runs asynchronously, and doesn't at all hold up the process of user creation. So, you will need to somehow wait for the function to complete before calling user.getIdToken(refresh: true).
This is precisely the thing I address in this blog post. The solution I offer does the following:
Client: Creates a user
Client: Waits for a document with the user's UID to be created in Firestore
Server: Auth onCreate function triggers
Server: Function does its work
Server: At the end, function writes data to a new document with the new user's UID
Client: Database listener triggers on the creation of the document
Then, you would add more more step on the client to refresh the ID token after it sees the new document.
The code given in the post is for web/javascript, but the process applies to any client. You just need to get the client to wait for the function to complete, and Firestore is a convenient place to relay that information, since the client can listen to it in real time.
Also read this post for a way to get a client to refresh its token immediately, based on claims written to a Firestore document.
Bottom line is that you're in for a fair amount of code to sync between the client and server.

MongoDB Stitch REST API - Payload Signature Verification

I am working on a SANDBOX Cluster & a new app created by me in MongoDB Stitch.
I need to understand "Payload Signature Verification" in MongoDB Stitch App. Lets say, I need to make a REST GET API, which will fetch me a list of products, but this API call must be authenticated ie. only registered/authenticated users will be able to make this call. MongoDB Stitch suggests below to do that:
https://docs.mongodb.com/stitch/services/webhook-requests-and-responses/#webhook-verify-payload-signature
But, i need to understand:
(1) Where to add this BODY & SECRET ? As per my knowledge, it must be kept in the stitch app, as you must not expose any of your secret keys in client side javascript.
(2) { "message":"MESSAGE" } is this configurable? if yes, what value should we add here?
This function must be coded in MongoDB Stitch App. That is clear. This function returns "hash" based on the "body" & "secret" you pass in earlier step.
And now, you must pass this hash in your API Request:
Now, the question is:
You can easily see any request which is being passed to server in developer tools, anybody can easily copy it & pass it same through POSTMAN. So:
-> How do i secure my requests? (FYI: I have also added "RULES", saying this request must execute only if the domain name contains lets say, www.mysite.com. But i am able to execute the request successfully from localhost.)
-> If, anybody can copy & paste my request in POSTMAN & run it. SO, what is the use of generating that HASH ?
-> How do i keep my request(s) tokens alive/valid for limited period of time, lets say request is valid only for next 5 minutes ? (i mean how do i do this in Stitch APP ? Where is that Option ?)
-> How do i get the refresh token ? & even if i get it somehow, how do i re-pass it to the request ?
All such queries are UN_ANSWERED in MongoDB Stich Documentation : https://docs.mongodb.com/stitch/
Basically i want to understand the full life-cycle of any GET/POST/PUT/PATCH/DELETE request of MongoDB Stitch App / Stitch REST APIs.
If anybody have used MongoDB Stich, please explain me.
I don't know your specific use-case, though I also had issues with creating an Authenticated HTTP REST API. My idea was: I already have all security rules and schemas defined in Stitch, now I want to access the data over HTTP still using the logic defined in Stitch and not rewriting everything.
I wasn't able to create such API with Stitch functions and Webhooks, though I created an API server in (literally) 1 hour with NodeJS Koa (express or any other framework would do) and Stitch server SDK:
// app.js
const Koa = require('koa')
const app = module.exports = new Koa()
const auth = require('./auth')
const router = require('./router')
app.use(auth())
app.use(router.routes())
app.use(router.allowedMethods())
// listen
if (!module.parent) {
app.listen(3000)
}
// auth.js
const { loginWithApiKey } = require('./stitch')
function auth () {
return async function auth (ctx, next) {
const apiKey = ctx.query.api_key
try {
await loginWithApiKey(apiKey)
} catch (e) {
ctx.throw(401, 'Not Authorized')
}
await next()
}
}
module.exports = auth
// router.js
const router = require('koa-router')()
const { BSON } = require('mongodb-stitch-server-sdk')
const { db } = require('./stitch')
router.get('/', async (ctx) => {
ctx.body = { message: 'Nothing to see, but you\'re good!' }
})
const COLLECTIONS_WHITELIST = [
'activities',
'expenses',
'projects',
'resources'
]
// List
router.get('/:collection', async (ctx) => {
const collection = ctx.params.collection
isValidCollection(ctx, collection)
ctx.body = await db
.collection(collection)
.find()
.toArray()
})
function isValidCollection (ctx, collection) {
// check if the collection is allowed in the API
if (!COLLECTIONS_WHITELIST.includes(collection)) {
ctx.throw(404, `Unknown API entity ${collection}`)
}
}
module.exports = router
I hope it helps

AppSync: Get user information in $context when using AWS_IAM auth

In AppSync, when you use Cognito User Pools as your auth setting your identity you get
identity:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
issuer: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
username: 'skillet',
claims:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
aud: '7re1oap5fhm3ngpje9r81vgpoe',
email_verified: true,
event_id: 'bb65ba5d-4689-11e8-bee7-2d0da8da81ab',
token_use: 'id',
auth_time: 1524441800,
iss: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
'cognito:username': 'skillet',
exp: 1524459387,
iat: 1524455787,
email: 'myemail#nope.com' },
sourceIp: [ '11.222.33.200' ],
defaultAuthStrategy: 'ALLOW',
groups: null }
However when you use AWS_IAM auth you get
identity:
{ accountId: '12121212121', //<--- my amazon account ID
cognitoIdentityPoolId: 'us-west-2:39b1f3e4-330e-40f6-b738-266682302b59',
cognitoIdentityId: 'us-west-2:a458498b-b1ac-46c1-9c5e-bf932bad0d95',
sourceIp: [ '33.222.11.200' ],
username: 'AROAJGBZT5A433EVW6O3Q:CognitoIdentityCredentials',
userArn: 'arn:aws:sts::454227793445:assumed-role/MEMORYCARDS-CognitoAuthorizedRole-dev/CognitoIdentityCredentials',
cognitoIdentityAuthType: 'authenticated',
cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7"' }
The Docs says that this is expected, https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html .
However, if you use AWS_IAM connected to Cognito (which is required to have unauthenticated access), how are you supposed to get at the User's username, email, sub, etc? I need access to the user's claims when using AWS_IAM type Auth.
For making User's username, email, sub etc. accessible through AppSync API, there's an answer for that: https://stackoverflow.com/a/42405528/1207523
To sum it up, you want to send User Pools ID token to your API (e.g. AppSync or API Gateway). Your API request is IAM authenticated. Then you validate the ID token in a Lambda function and now you have your validated IAM user and User Pools data together.
You want to use the IAM's identity.cognitoIdentityId as primary key for you User table. Add the data included in ID token (username, email, etc.) as attributes.
This way you can make user's claims available through you API. Now, for example, you can set $ctx.identity.cognitoIdentityId as the owner of an item. Then maybe other users can see the name of the owner via GraphQL resolvers.
If you need to access the user's claims in your resolver I'm afraid that doesn't seems to be possible at the moment. I have made a question about this as it would be very helpful for authorization: Group authorization in AppSync using IAM authentication
In this case, instead of using a resolver you could use Lambda as a data source and retrieve the user's claims from the above-mentioned User table.
It's all a bit difficult at the moment :)
Here is bad answer that works. I notice that cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7" contains the Cognito user's sub (the big after CognitoSignIn). You can extract that with a regex and use the aws-sdk to get the user's info from cognito user pool.
///////RETRIEVE THE AUTHENTICATED USER'S INFORMATION//////////
if(event.context.identity.cognitoIdentityAuthType === 'authenticated'){
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
//Extract the user's sub (ID) from one of the context indentity fields
//the REGEX in match looks for the strings btwn 'CognitoSignIn:' and '"', which represents the user sub
let userSub = event.context.identity.cognitoIdentityAuthProvider.match(/CognitoSignIn:(.*?)"/)[1];
let filter = 'sub = \"'+userSub+'\"' // string with format = 'sub = \"1a072f08-5c61-4c89-807e-417d22702eb7\"'
let usersData = await cognitoidentityserviceprovider.listUsers( {Filter: filter, UserPoolId: "us-west-2_KsyTKrQ2M",Limit: 1}).promise()
event.context.identity.user=usersData.Users[0];
}
It's a bad answer because you are pinging the User Pool database instead of just decoding a JWT.
Here is my answer. There was a bug in the appSync client library that would overwrite all custom headers. That has since been fixed. Now you can pass down custom headers that will make it all the way to you resolvers, which I pass to my lambda functions (again, note I am using lambda datasourcres and not using dynamoDB).
So I attach my logged in JWT on the client side and, server side in my lambda function, I decode it. You need the public key created by cognito to validate the JWT. (YOU DO NOT NEED A SECRET KEY.) There is a "well known key" url associated with every user pool which I ping the first time my lambda is spun up but, just like my mongoDB connection, it is persisted between lambda calls (at least for a while.)
Here is lambda resolver...
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');
const request = require('request-promise-native');
const _ = require('lodash')
//ITEMS THAT SHOULD BE PERSISTED BETWEEN LAMBDA EXECUTIONS
let conn = null; //MONGODB CONNECTION
let pem = null; //PROCESSED JWT PUBLIC KEY FOR OUR COGNITO USER POOL, SAME FOR EVERY USER
exports.graphqlHandler = async (event, lambdaContext) => {
// Make sure to add this so you can re-use `conn` between function calls.
// See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas
lambdaContext.callbackWaitsForEmptyEventLoop = false;
try{
////////////////// AUTHORIZATION/USER INFO /////////////////////////
//ADD USER INFO, IF A LOGGED IN USER WITH VALID JWT MAKES THE REQUEST
var token = _.get(event,'context.request.headers.jwt'); //equivalen to "token = event.context.re; quest.headers.alexauthorization;" but fails gracefully
if(token){
//GET THE ID OF THE PUBLIC KEY (KID) FROM THE TOKEN HEADER
var decodedToken = jwt.decode(token, {complete: true});
// GET THE PUBLIC KEY TO NEEDED TO VERIFY THE SIGNATURE (no private/secret key needed)
if(!pem){
await request({ //blocking, waits for public key if you don't already have it
uri:`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`,
resolveWithFullResponse: true //Otherwise only the responce body would be returned
})
.then(function ( resp) {
if(resp.statusCode != 200){
throw new Error(resp.statusCode,`Request of JWT key with unexpected statusCode: expecting 200, received ${resp.statusCode}`);
}
let {body} = resp; //GET THE REPSONCE BODY
body = JSON.parse(body); //body is a string, convert it to JSON
// body is an array of more than one JW keys. User the key id in the JWT header to select the correct key object
var keyObject = _.find(body.keys,{"kid":decodedToken.header.kid});
pem = jwkToPem(keyObject);//convert jwk to pem
});
}
//VERIFY THE JWT SIGNATURE. IF THE SIGNATURE IS VALID, THEN ADD THE JWT TO THE IDENTITY OBJECT.
jwt.verify(token, pem, function(error, decoded) {//not async
if(error){
console.error(error);
throw new Error(401,error);
}
event.context.identity.user=decoded;
});
}
return run(event)
} catch (error) {//catch all errors and return them in an orderly manner
console.error(error);
throw new Error(error);
}
};
//async/await keywords used for asynchronous calls to prevent lambda function from returning before mongodb interactions return
async function run(event) {
// `conn` is in the global scope, Lambda may retain it between function calls thanks to `callbackWaitsForEmptyEventLoop`.
if (conn == null) {
//connect asyncoronously to mongodb
conn = await mongoose.createConnection(process.env.MONGO_URL);
//define the mongoose Schema
let mySchema = new mongoose.Schema({
///my mongoose schem
});
mySchema('toJSON', { virtuals: true }); //will include both id and _id
conn.model('mySchema', mySchema );
}
//Get the mongoose Model from the Schema
let mod = conn.model('mySchema');
switch(event.field) {
case "getOne": {
return mod.findById(event.context.arguments.id);
} break;
case "getAll": {
return mod.find()
} break;
default: {
throw new Error ("Lambda handler error: Unknown field, unable to resolve " + event.field);
} break;
}
}
This is WAY better than my other "bad" answer because you are not always querying a DB to get info that you already have on the client side. About 3x faster in my experience.
If you are using AWS Amplify, what I did to get around this was to set a custom header username as explained here, like so:
Amplify.configure({
API: {
graphql_headers: async () => ({
// 'My-Custom-Header': 'my value'
username: 'myUsername'
})
}
});
then in my resolver I would have access to the header with:
$context.request.headers.username
As explained by the AppSync's docs here in the section Access Request Headers
Based on Honkskillets answer, I have written a lambda function that will return you the user attributes. You just supply the function with the JWT.
const jwt = require("jsonwebtoken");
const jwkToPem = require("jwk-to-pem");
const request = require("request-promise");
exports.handler = async (event, context) => {
try {
const { token } = event;
const decodedToken = jwt.decode(token, { complete: true });
const publicJWT = await request(
`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`
);
const keyObject = JSON.parse(publicJWT).keys.find(
key => key.kid == decodedToken.header.kid
);
const pem = jwkToPem(keyObject);
return {
statusCode: 200,
body: jwt.verify(token, pem)
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
body: error.message
};
}
};
I use it in Appsync where I create Pipeline resolvers and add this function whenever I need user attributes. I supply the JWT by grabbing it from the header in the resolver using $context.request.