Could a callable cloud function that delete users be abused so it can delete users by only id? - google-cloud-firestore

I have a callable cloud function on the frontend, that gets a sub-user id from front-end pass it to the cloud function, and then the cloud function delete that user and also deletes his doc from the collection...
my question is could someone get the id of some user and use that function and start popping requests using this function to delete users left and right ?
it make sense that this could function won't follow any rules, so I consider this to be a major security risk if implemented in the wrong way any idea how to improve security on this and guard against any abuse attempts.
Front end callable function
const functions = getFunctions();
const deleteClient = httpsCallable(functions, 'deleteClient');
deleteClient({ uid: 'clientId' })
.then((result: any) => {
// Read result of the Cloud Function.
/** #type {any} */
// const data = result.data;
// const sanitizedMessage = data.text;
console.log(result);
})
.catch((err: any) => {
alert(err);
});
Cloud Function
export const deleteClient = functions.https.onCall((data, context) => {
admin
.auth()
.deleteUser(data.uid)
.then(() => {
console.log('Successfully deleted user');
})
.catch((error: any) => {
console.log('Error deleting user:', error);
});
db.collection('ClientsData').doc(data.uid).delete();
});

It indeed sounds like you created a security risk, and is also precisely why Firebase Authentication only allows deleting the currently signed-in user in its client-side SDKs.
You'll have to implement some sort of authorization scheme in your Cloud Functions code. This takes a two step process:
Pass the identity of the signed-in user making the call from the client to the server, and use it there to establish who is making the call. Since you're using Callable Cloud Functions, this is already done for you and the user is available in the context.auth variable in your Cloud Functions code.
Determine whether the user is authorized to perform the operation. This is typically done by having a list of authorized users, and then checking of the context.auth.uid who made the call is in that list. The list could be stored in your database too of course, so that you can update it without making changes to the code.

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.

Check if user phone_number already exist in aws cognito userpool

I am using serverless lambda functions on aws for user authentication in cognito user-pool. I am asking the user only for his phone_number and sending him otp for verification. The problem arises, when the user signs out and sign-in again. I am unable to decide on when to call signUp or sign-in for the user.
I am guessing that, I need a lambda call to be triggered before the pre-signup, which verifies that the user phone number already exists in the user-pool, and I need to call the Amplify.Auth.signIn api from the client. If not then call Amplify.Auth.signUpapi from the client. But I am unable to find any document for that. I am using flutter as my front-end. Please help.
My pre-signup lambda functions looks something like this:
exports.handler = async (event) => {
console.log('Received EVENT', JSON.stringify(event, null, 2));
event.response.autoConfirmUser = true;
event.response.autoVerifyPhone = true;
return event;
};
You can use the CognitoIdentityServiceProvider.listUsers for this.
const cognitoIdentityServiceProvider = new CognitoIdentityServiceProvider();
const result = await this.cognitoIdentityServiceProvider.listUsers({
UserPoolId: process.env.USER_POOL_ID,
Filter: `phone_number = \"${phoneNumber}\"`
}).promise();
return result.Users.length > 0 ? result.Users[0].Username : undefined;

Can I update a user document whenever a user updates his authentication profile?

I am working with flutter and I have a AuthenticationProvider. Whenever my user signs in with his phone I update his profile as well. But my problem is that auth users can't be queried. So I read that I should keep a separate user collection. Now my question is, is it possible to update a user document in my user collection whenever a user updates his auth profile? I would like to do this with cloud functions but I noticed that there is only a create and delete? So how can I do this?
This is what I currently have
Authentication Provider
Future<void> _verificationComplete(BuildContext context, AuthCredential authCredential, userInfo.UserInfo userInfo) async {
AuthResult authResult = await FirebaseAuth.instance.signInWithCredential(authCredential);
final userUpdateInfo = UserUpdateInfo();
userUpdateInfo.displayName = userInfo.name;
userUpdateInfo.photoUrl = userInfo.photoUrl;
await authResult.user.updateProfile(userUpdateInfo);
await authResult.user.reload();
user = UserModel.fromFirebase(authResult.user);
_status = AuthenticationStatus.authenticated;
notifyListeners();
}
Cloud function
export const onUserCreated = functions.region('europe-west1').auth.user().onCreate(async user => {
const privateUserData = {
activeGroup: '',
cloudMessagingToken: '',
}
const publicUserData = {
name: '',
photoUrl: '',
}
const promises = [];
promises.push(firestore.collection('users').doc(user.uid).collection('private').doc('data').set(privateUserData));
promises.push(firestore.collection('users').doc(user.uid).collection('public').doc('data').set(publicUserData));
return await Promise.all(promises);
});
There is no Cloud Functions trigger for when a user updates their Firebase Authentication profile. I'd highly recommend filing a feature request for that, as it's much missed.
For now, the closest you can get is with a Cloud Function that you call directly from the application code. The two options there are:
Have your application code call the Firebase Authentication API first, then when that completes, have it call your custom Cloud Function to update the database too.
Have your application code call the Cloud Function immediately, and then have the Cloud Function update both the user profile and the database.
I somehow often do the first one, but see more developers take the second approach. I think their approach is probable simpler, but I just haven't gotten around to it yet. :)

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