Firebase let authenticated user create their own user entry - swift

I wanted to know if it's possible that a newly authenticated user can insert only their information to the realtime database.
The flow would be something like:
User authenticates via my app where he has to input
Email
Username
...
The user is authenticated (via Firebase Authentication) and has a uid
User writes a new DB entry in the /users/{uid} field of his/her. However, they are not allowed to set the balances field below.
Email
Username
...
He/She should only be able to write and read his/her own /users/{uid} object.
I've tried these rules so far, but none of them lets me create a new /users/{uid}
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
"balances": {
".write": false
},
"userName": {
".write": "$uid === auth.uid",
},
"email": {
".write": "$uid === auth.uid",
},
"phoneNumber": {
".write": "$uid === auth.uid",
}
}
}
}
}
The code I want to insert:
// ...
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
if let error = error {
self.error = error.localizedDescription
return
}
if let authResult = authResult {
// Auth successful
User.firebaseUser = authResult.user
print("Firebace UserId: \(authResult.user.uid)")
let ref: DatabaseReference! = Database.database().reference()
ref.child("users").child(authResult.user.uid).setValue([
"userName": self.userName,
"email": self.email
])
// ...
}
}
My Swift error message:
2022-05-26 16:55:55.043864+0200 Project[15240:1265346] 9.0.0 - [FirebaseDatabase][I-RDB038012] setValue: or removeValue: at /users/EZ2bNl3rMANtLtn1G9Wfqi0Ivjl1 failed: permission_denied
Thank you in advance!

Related

node-oidc-provider 7.12.0 (JWT, authorization_code) invalid_token error on UserInfo endpoint (/me)

i have some issues getting the UserInfo endpoint working using JWT AccessTokens, it works fine with default settings when commenting out the resourceIndicators section.
I can get the access token using PostMan without issues, but when posting on the UserInfo (/me) endpoint the Bearer AccessToken, i got an invalid_token error.
here is my code:
const {Provider} = require('oidc-provider');
let hostname = process.env.HOSTNAME;
if (hostname === undefined) {
hostname = "http://localhost"
}
const port = process.env.PORT || 3000;
if (port !== 80 && port !== 443) {
hostname = hostname + ':' + port
}
const users = [
{
"id": "user1",
"email": "user1#example.com",
"authentication_method_reference": "mfa"
}
]
const clients = [
{
"client_id": "client-1",
"client_secret": "client-1-secret",
"redirect_uris": [
"http://localhost:3000"
]
}
]
async function findAccount (ctx, id) {
// This would ideally be just a check whether the account is still in your storage
let account = users.find(user => {
return user.id === id;
})
if (!account) {
return undefined;
}
return {
accountId: id,
async claims() {
return {
sub: id,
email: account.email,
amr: [account.authentication_method_reference]
};
},
};
}
const configuration = {
clients: clients,
conformIdTokenClaims: false,
features: {
devInteractions: {
enabled: true
},
resourceIndicators: {
defaultResource: (ctx, client, oneOf) => {
return hostname;
},
enabled: true,
getResourceServerInfo: (ctx, resourceIndicator, client) => {
console.log('get resource server info', client);
return ({
audience: resourceIndicator,
scope: 'openid',
accessTokenTTL: 2 * 60 * 60,
accessTokenFormat: 'jwt',
});
},
useGrantedResource: (ctx, model) => { return true; }
}
},
claims: {
openid: [
'sub',
'email',
'amr'
]
},
cookies: {
keys: 'super,secret'.split(',')
},
pkce: {
required: () => false
},
// Used to skip the 'approval' page
async loadExistingGrant(ctx) {
const grantId = (ctx.oidc.result
&& ctx.oidc.result.consent
&& ctx.oidc.result.consent.grantId) || ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId);
if (grantId) {
// keep grant expiry aligned with session expiry
// to prevent consent prompt being requested when grant expires
const grant = await ctx.oidc.provider.Grant.find(grantId);
// this aligns the Grant ttl with that of the current session
// if the same Grant is used for multiple sessions, or is set
// to never expire, you probably do not want this in your code
if (ctx.oidc.account && grant.exp < ctx.oidc.session.exp) {
grant.exp = ctx.oidc.session.exp;
await grant.save();
}
return grant;
} else {
const grant = new ctx.oidc.provider.Grant({
clientId: ctx.oidc.client.clientId,
accountId: ctx.oidc.session.accountId,
});
grant.addOIDCScope('openid');
grant.addResourceScope(hostname, 'openid');
await grant.save();
return grant;
}
},
extraTokenClaims: async (ctx, token) => {
return findAccount(ctx, token.accountId).then(account => {
return account.claims()
})
},
findAccount: findAccount
};
const oidc = new Provider(hostname, configuration);
function handleServerError(ctx, err) {
console.log(err);
}
function handleGrantErrors({headers: {authorization}, oidc: {body, client}}, err) {
console.log(err);
}
function handleAccessToken(token) {
console.log(token);
}
oidc.on('grant.error', handleGrantErrors);
oidc.on('introspection.error', handleGrantErrors);
oidc.on('revocation.error', handleGrantErrors);
oidc.on('server_error', handleServerError);
oidc.on('access_token.issued', handleAccessToken);
oidc.listen(port, () => {
console.log(`oidc-provider listening on port ${port}.`)
})
I tried different configurations without success, the generated JWT AccessToken looks fine to me (see bellow), but i'm unable to query the UserInfo endpoint with it.
{
"sub": "user1",
"email": "user1#example.com",
"amr": [
"mfa"
],
"jti": "-7gURc8Y1SXqOXhWR691i",
"iat": 1668777371,
"exp": 1668784571,
"scope": "openid",
"client_id": "client-1",
"iss": "http://localhost:3000",
"aud": "http://localhost:3000"
}
Thanks in advance.
As per the module documentation's userinfo feature.
Its use requires an opaque Access Token with at least openid scope that's without a Resource Server audience.
In essence, this implementation's userinfo endpoint will not work with JWT Access Tokens that are issued for a particular resource server. This is because the userinfo endpoint is a resource for the client and if it was callable with an access token that was sent to a resource server, that resource server can turn around and query userinfo which is not the intended use of the userinfo endpoint.
In cases when JWT Access Tokens are issued the client will get all scope requested userinfo claims in the ID Token it receives, removing the need to call userinfo.

How do I write the data to Real-time Database firebase flutter?

I'm trying to write data to the realtime database by first logging in users always using firebase (google_auth).
But there is something wrong with my rules ... if I set write and read "true" they obviously go, the same if I just use "auth! = Null" but when I use these rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth! = null && auth.uid == $ uid",
".write": "auth! = null && auth.uid == $ uid"
}
}
}
}
No longer goes. So I assume the problem is that auth.uid is not the same as uid. I am attaching the code used for authentication and data submission.
Login / registration:
Future <UserCredential> signInWithGoogle () async {
final GoogleSignInAccount? userGoogle = await GoogleSignIn (). signIn ();
final GoogleSignInAuthentication? Google user data =
await userGoogle? .authentication;
final credentialsGoogle = GoogleAuthProvider.credential (
accessToken: Google User data? .accessToken,
idToken: Google User data? .idToken,
);
return await FirebaseAuth.instance.signInWithCredential (Google credentials);
}
Data sending:
DatabaseReference ref = FirebaseDatabase.instance.ref ();
await ref.set ({
"users": {
"$uid": {
"name": "John",
}
}
});
uid is fetched like this:
checkLogin () {
FirebaseAuth.instance.authStateChanges (). Listen ((User? User) {
if (user! = null) {
username = user.displayName;
uid = user.uid;
}
});
}
i think it's because of wrong spacing.. edited the rules. Try this please
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}

Firebase security rules continue to ask "Consider adding ".indexOn":" even after adding indexOn

I have added a function to search user on firebase.
Here is the code:
func searchUser(search: String, includeCurrentUser: Bool = true, completion: #escaping ([User]) -> (), withCancel cancel: ((Error) -> ())?) {
Database.database().reference().child("users").queryOrdered(byChild: "username").queryStarting(atValue: search , childKey: "username").queryEnding(atValue: search + "\u{f8ff}", childKey: "username").observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else {
completion([])
return
}
var users = [User]()
dictionaries.forEach({ (key, value) in
if !includeCurrentUser, key == Auth.auth().currentUser?.uid {
completion([])
return
}
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
users.append(user)
})
users.sort(by: { (user1, user2) -> Bool in
return user1.username.compare(user2.username) == .orderedAscending
})
completion(users)
}){ (err) in
print("Failed to fetch all users from database:", (err))
cancel?(err)
}
}
When searchUser is called, my Xcode console states:
[Firebase/Database][I-RDB034028] Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "username" at /users to your security rules for better performance
Here are my firebase rules:
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": true,
".write": true,
"posts" : {
".indexOn": "creationDate"
},
"purchaser" : {
"$postID" : {
".indexOn": ".value"
}
},
"users" : {
"$uid": {
".indexOn" : "username"
}
}
}
}
And here is my database structure:
So the searchUser function works, but the xcode console continue to asks for .indexOn for username.
Why is going wrong here?
Indexes needs to be defined on the location where you use them in your query. So to define an index on /users with the username property of each child node:
"users" : {
".indexOn" : "username"
}

json-server: Default JSON for non-matching route

I want to fake a login API with the route /api/login/:userName/:password that would return { "success": false, "userName": "", "password": "" } in case userName or password is not found in the database. How can I achieve this with json-server?
So far I have this database JSON:
{
"login": [
{ "success": true, "userName": "zaphod", "password": "galaxy" }
],
"invalidLogin": [
{ "success": false, "userName": "", "password": "" }
]
}
invalidLogin is currently not used and should be used for every login route with unknown userName or password.
This is my server.js:
const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('db.json');
const middlewares = jsonServer.defaults();
server.use(middlewares);
// Rewrite rules
server.use(jsonServer.rewriter({
'/login/:userName/:password': "/login?userName=:userName&password=:password"
}))
server.use('/api', router);
server.listen(3001, () => {
console.log('JSON Server is running')
});
You can achieve something like this using router.render function:
router.render = (req, res, next) => {
if (req.path === '/login') {
if (res.locals.data.length !== 0) {
res.jsonp(res.locals.data);
} else {
res.jsonp([
{ "success": false, "userName": "", "password": "" }
])
}
} else {
res.jsonp(res.locals.data);
}
}
Basically this router code will check if there are non-empty response to /login/.../... and afterwards replace it with error JSON on empty login request. You can upgrade this mock's logic further.
Aforementioned code can be placed after rewrite rules in server.js (there was a minor error in your code, you have to write it with api, reference issue):
server.use('/api', jsonServer.rewriter({
"/login/:userName/:password": "/login?userName=:userName&password=:password",
}))

Data not updated on Production DB

I have a weird situation on my project. I have 2 entities: children and users. Users create children, so on children, I have a reference to their owners and on users I have a list of the children they have created.
When I add a new child to firebase, the user owner of that child has his list of children updated to add that new child.
The thing is: on the staging environment everything works as it should be. But on the production environment, the child is added to the children, but not to the list of children on users.
Both databases have the same rules.
I've got to this project when it was already partially developed, so I can't even find where specifically the user should be updated. It seems to me that when you update a child, the users entity is automagically updated (using breakpoints I could verify it actually happens on the same step). I'm kind of new to Firebase so I am really lost here.
Can anyone try to help me?
Here are the database rules:
{"rules": {
"children": {
"$uid": {
".validate": "newData.hasChildren(['first_name'])",
"date_of_birth": {
".validate": "newData.isString() && newData.val().matches(/^(19|20)\\d\\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/)"
},
"first_name": {
".validate": "newData.isString() && newData.val().length > 0"
},
"users": {
"$key2": {
".validate": "root.child('users').child($key2).val() != null && newData.isString() && newData.val() == 'parent'"
},
".validate": "newData.hasChildren()"
},
"$other": {
".validate": "false"
},
".read": "data.child('users').child(auth.uid).val() != null",
".write": "data.child('users').child(auth.uid).val() == 'parent' || newData.child('users').child(auth.uid).val() == 'parent'"
}
},
"users": {
".indexOn": [
"email"
],
"$uid": {
".validate": "newData.hasChildren()",
"email": {
".validate": "newData.isString() && newData.val() == auth.token.email"
},
"first_name": {
".validate": "newData.isString()"
},
"is_subscribed": {
".validate": "newData.isBoolean()"
},
"children": {
"$key5": {
".validate": "root.child('children').child($key5).val() != null && newData.isString() && newData.val() == 'parent'"
},
".validate": "newData.hasChildren()"
},
"$other": {
".validate": "false"
},
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
and the code that adds children to it.
var entityDatabase: DatabaseReference = Database.database().reference().child(“children”).child(did)
entityDatabase.keepSynced(true)
var firstName: String?
var users: [String: String] = [:]
var params: [String: Any] {
var strings: [String: String] = [:]
strings["first_name"] = firstName
let keysToRemove = strings.keys.filter { strings[$0] == "" }
for key in keysToRemove {
strings.removeValue(forKey: key)
}
var dict: [String: Any] = strings
dict["users"] = users
return dict
}
guard let user = Firebase.Auth.auth().currentUser else {
return
}
guard firstName?.isEmpty == false else {
return
}
users[user.uid] = "parent"
entityDatabase.updateChildValues(params) { [weak self] (error, _) in
self?.observe()
guard error == nil else {
return
}
switch self?.updatedImage {
case let image?:
self?.save(image: image, completion: completion)
case nil:
}
}