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);
});
});
Related
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
I'm trying to use a constant in my API. This constant is an array where I can push or pop objects. I populate it through API calls. This is the code:
const washes = [];
export default async (req, res) => {
if(req.method === 'GET')
{
res.json({washes});
}
if(req.method === 'POST')
{
washes.push(req.body);
console.log(washes);
res.json({washes});
}
}
I make calls from the front-end to populate the array and to retrieve it. The problem is that, sometimes I get an empty array an sometimes I get the expected array. Or after a while I always get the empty array, like if it was restarted.
I deployed the application on Vercel.
Is there something I'm missing from the back-end functionality, or is it related to Vercel?. Last but not least is it a good practice to use variables or constants in back-end.?
You cant use an array as storage. You need to connect it to a database of sorts.
When you use this endpoint it will only retrieve the correct array sometimes, because it's stored in a temporary memory on the server.
Look into firebase if you easily want to setup up a project and store information in a database.
https://firebase.google.com/docs/web/setup
Another way is to use a .json file in the back-end
Read more here: https://jasonwatmore.com/post/2021/08/28/next-js-read-write-data-to-json-files-as-the-database
USER EXAMPLE
const fs = require('fs');
// users in JSON file for simplicity, store in a db for
production applications
let users = require('data/users.json');
export const usersRepo = {
getAll: () => users,
getById: id => users.find(x => x.id.toString() === id.toString()),
find: x => users.find(x),
create,
update,
delete: _delete
};
function create(user) {
// generate new user id
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
// set date created and updated
user.dateCreated = new Date().toISOString();
user.dateUpdated = new Date().toISOString();
// add and save user
users.push(user);
saveData();
}
function update(id, params) {
const user = users.find(x => x.id.toString() === id.toString());
// set date updated
user.dateUpdated = new Date().toISOString();
// update and save
Object.assign(user, params);
saveData();
}
// prefixed with underscore '_' because 'delete' is a reserved word in javascript
function _delete(id) {
// filter out deleted user and save
users = users.filter(x => x.id.toString() !== id.toString());
saveData();
}
// private helper functions
function saveData() {
fs.writeFileSync('data/users.json', JSON.stringify(users, null, 4));
}
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.
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
I am using Identity Server 3. I have couple applications ie. Client configured and have few users configured. How do i establish the relationship between User and a Client and also view all applications that the selected User has access to.
Update 1
I am sorry if question was confusing. On IdSvr3 home page, there is a link to revoke application permissions. I am guessing in order to revoke the permission you have to first establish the relationship between user and application.
and i wanted to know how to establish that permission when i add new user?
There's no direct way to limit one or multiple users to a certain client. This is where you should think about implementing your own custom validation. Fortunately, the IdentityServer provides an extensibility point for this kind of requirement.
ICustomRequestValidator
You should implement this interface to further validate users to see if they belong to certain clients and filter them out. You can look into the user details by looking at ValidatedAuthorizeRequest.Subject. This custom validator will start after validating optional parameters such as nonce, prompt, arc_values ( AuthenticationContextReference ), login_hint, and etc. The endpoint is AuthorizeEndPointController and the default implementation of the interface for the tailored job is AuthorizeRequestValidator and its RunValidationAsync. You should take a look at the controller and the class.
Implementation tip
By the time the custom request validation begins, a Client reference will be presented in ValidatedAuthorizeRequest. So all you need to do would be matching the client id or some other identifiers you think you need to verify the client. Probably, you might want to add a Claim key-value pair to your client which you want to allow a few users.
Maybe something like this.
new InMemoryUser{Subject = "870805", Username = "damon", Password = "damon",
Claims = new Claim[]
{
new Claim(Constants.ClaimTypes.Name, "Damon Jeong"),
new Claim(Constants.ClaimTypes.Email, "dmjeong#email.com"),
new Claim(Constants.ClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
}
}
Assume you have above user, then add the subject id to the claim of a client like below.
new Client
{
ClientName = "WPF WebView Client Sample",
ClientId = "wpf.webview.client",
Flow = Flows.Implicit,
.
.
.
// Add claim for limiting this client to certain users.
// Since a claim only accepts type and value as string,
// You can add a list of subject id by comma separated values
// eg ( new Claim("BelongsToThisUser", "870805, 870806, 870807") )
Claims = new List<Claim>
{
new Claim("BelongsToThisUser", "870805")
}
},
And then just implement the ICustomRequestValidator and try to match the Claim value with the given user in its ValidateAuthorizeRequestAsync.
public class UserRequestLimitor : ICustomRequestValidator
{
public Task<AuthorizeRequestValidationResult> ValidateAuthorizeRequestAsync(ValidatedAuthorizeRequest request)
{
var clientClaim = request.Client.claims.Where(x => x.Type == "BelongsToThisUser").FirstOrDefault();
// Check is this client has "BelongsToThisUser" claim.
if(clientClaim != null)
{
var subClaim = request.Subject.Claims.Where(x => x.Type == "sub").FirstOrDefault() ?? new Claim(string.Empty, string.Empty);
if(clientClaim.Value == userClaim.Value)
{
return Task.FromResult<AuthorizeRequestValidationResult>(new AuthorizeRequestValidationResult
{
IsError = false
});
}
else
{
return Task.FromResult<AuthorizeRequestValidationResult>(new AuthorizeRequestValidationResult
{
ErrorDescription = "This client doesn't have an authorization to request a token for this user.",
IsError = true
});
}
}
// This client has no access controls over users.
else
{
return Task.FromResult<AuthorizeRequestValidationResult>(new AuthorizeRequestValidationResult
{
IsError = false
});
}
}
public Task<TokenRequestValidationResult> ValidateTokenRequestAsync(ValidatedTokenRequest request)
{
// your implementation
}
}
Time to DI
You need to inject your own dependency when you configure up your IdentityServer. The authorization server uses IdentityServerServiceFactory for registering dependencies.
var factory = new IdentityServerServiceFactory();
factory.Register(new Registration<ICustomRequestValidator>(resolver => new UserRequestLimitor()));
Then Autofac; the IoC container in IdentityServer will do the rest of the DI jobs for you.