Redirect user to its subdomain in ReactJs - node.js

I am building a react website where I assign a subdomain to the user when they sign up according to the username they enter, let's say their username is john, then the subdomain will be john.mydomain.com.
I would like to know how to redirect a user to its subdomain after they signup. I have access to the user data immediately after signup, such as username.
Sign up code:
export const signup = (formData, history) => async (dispatch) => {
try {
// sign up the user
const { data } = await api.signUp(formData);
dispatch({ type: "AUTH", data });
toast.success("Signed Up successfully");
history.push("/");
} catch (error) {
dispatch({ type: "ERROR", data: error?.response?.data });
toast.error(error?.response?.data?.message);
}
};
Thank You.

Assuming the subdomain is on the same host you can use History API or fallback on location:
const url = `${window.location.protocol}//${data.username}.${window.location.hostname}`
if (window.history) {
window.history.pushState(data, '', url) // data included to subdomain
} else {
const stringifiedData = JSON.stringify(data)
const encodedData = btoa(stringifiedData) // base64 encode
window.location.href = `${url}?data=${encodedData}
}
And then on the subdomain:
let data
if (window.history) {
data = window.history.state // Access the state passed when signing up
// save to localstorage or something
} else {
const params = new URLSearchParams(window.location.search)
const encodedData = params.get('data')
const decodedData = atob(encodedData) // base64 decode
const originalData = JSON.parse(decodedData)
// save to localstorage or something
}

In a new sign up, your server can generate a one time random string, which can be returned to your front end.
Then when you get the random string, you can pass it to your next page
const { data } = await api.signUp(formData);
dispatch({ type: "AUTH", data });
toast.success("Signed Up successfully");
window.location.href = `yourpath?authkey=${data.onetimekey}`
On arrival to your new page, the new page should get the authkey, pass it to backend, to confirm it's authentication status, and send back the user data you need, before you save it to your store in the new domain page.
You need to generate a random string (to be saved to a database of sort), because you should not be passing any private information via the query string.

Related

FETCH request doesn't work unless I refresh my page

I have a React-Ionic web app doing some queries to a custom NodeJS server. I can read users but I cant create one unless I refresh my page. All of my GET and POST queries are working properly after I refresh my page.
Here is my function to create a User. Every log are showing except 'USR SUCESSFULLY LAUNCH...'
export async function createUser(p_user: StudentSkeletonDB) {
//Normalize name guid familyname and comments
//Removing all specials characters
console.log("USR STARTING TO CREATE :")
let newUser: StudentSkeletonDB = p_user;
newUser.firstname = newUser.firstname.replace(/[~`!##$%^&*()+={}\[\];:\'\"<>.,\/\\]/g, '');
newUser.guid_id = newUser.guid_id.replace(/[~`!##$%^&*()+={}\[\];:\'\"<>.,\/\\]/g, '');
newUser.familyname = newUser?.familyname != undefined ? newUser.familyname.replace(/[~`!##$%^&*()+={}\[\];:\'\"<>.,\/\\]/g, '') : "";
newUser.comments = newUser?.comments != undefined ? newUser.comments.replace(/[~`!##$%^&*()+={}\[\];:\'\"<>.,\/\\]/g, '') : "";
console.log("USR NORMALIZED :")
console.log(newUser)
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Content-Lenght', '' + JSON.stringify(newUser).length);
var data = new FormData();
data.append("json", JSON.stringify(newUser));
console.log("USR SENDING... :")
return await fetch("http://127.0.0.1:8083/signup", {
headers: myHeaders,
method: 'POST',
body: JSON.stringify(newUser)
}).then((s) => {
console.log("USR SUCCESSFULLY LAUNCH... :")
return s;
});
}
On my NodeJS Server, even the first log is not showing.
exports.postSignup = async(req, res, next) => {
//getting user data from request body
console.log("STARTING TO CREATE USER")
const { guid_id, firstname, familyname, _password, birthday, usertype, class_id, login, filenumber, avatar, language, isactive, ef_sessionpid, comments, connected, created, simu_password, gender, driving_school_code, nb_connection } = req.body;
try {
const user = new User({
guid_id,
firstname,
familyname,
_password,
birthday,
usertype,
class_id,
login,
filenumber,
avatar,
language,
isactive,
ef_sessionpid,
comments,
connected,
created,
simu_password,
gender,
driving_school_code,
nb_connection
});
const result = await user.createUser();
result.send(user);
} catch (error) {
const errorToThrow = new Error();
switch (error.code) {
case '23505':
errorToThrow.message = 'User already exists';
errorToThrow.statusCode = 403;
break;
default:
errorToThrow.statusCode = 500;
}
//pass error to next()
next(errorToThrow);
}
};
I repeat it, but if I refresh any page of my web app, then create a User, everything works properly and al my logs (even NodeJS) are showing. My newUser object is fine even without refreshing, it look like fetch itself doesnt work.
I tried to change fetch url to something totally wrong like return await fetch("http://127.0.0.1:8083/dzedzededze/dzedze)" and it doesn't even raise an error.
FIXED IT !
The previous request on my NodeJS server was crashing something.
All POST requests were in a queue.
There was multiples error in my code like Content-Lenght. I'm not very good in english : 'ght' and 'gth' are a big weakness of mine.
data was not used either.

Find one user on moongose

I have the following problem to solve: I have multiple users in a database (mongoDB). Pass the following, in a specific url, for example: "my-page/users/here-goes-the-name-of-the-user". When displaying the username in the url, I convert it to lowercase and replace the spaces with hyphens: Husdady Mena ===> /husdady-mena.
In the database, the user names are as shown before converting the name to lowercase, eg: Husdady Mena, Donald Trump, Will Smith, etc.
I need to somehow filter a user, which is in the url as /husdady-mena with the database user: Husdady Mena, in order to obtain his information, how could that filter be done in mongoose?
A clear example with js:
const users = [{ name: 'Husdady Mena' }, {name:'Donald Trump'}, {name:'Will Smith'}, {name:'Dubá Solis'}]
const username = "dubá-solis"
const url = `my-page.com/users/${username}`
const userToFilter = url.substring(url.lastIndexOf("/") + 1)
const userFound = users.find(({ name }) => {
const usernameToLowerCase = name.toLowerCase().replace(/\s/gim, "-")
return userToFilter === usernameToLowerCase
})
console.log(userFound)
If the example is not clear to you: I make a request from the frontend to the backend to obtain a specific user, I need to obtain the user by name, it is not necessary to obtain it by id. In the url something like: my-page/users/dubá-solis is painted, I get the name of the user, which is: dubá-solis. in the backend is something like:
// Librarys
const { Router } = require('express')
const router = Router()
// Models
const User = require('#models/users/User')
router.get(
'/users/:username',
function(req, res) {
try {
// req.params.username === "dubá-solis"
const userFound = await User.findOne({ name: req.params.username })
// In database, the username is Dubá Solis, that's why it returns me null
return res.status(200).json(userFound)
} catch(err) {
console.log('[UserFound.err]', err)
}
}
)
module.exports = router
Why not just convert the username and pass that to the query function? Something like:
const convertUsername = username => {
const nameParts = username.split('-');
return nameParts.map(n => n.slice(0, 1).toUpperCase() + n.slice(1))
.join(' ');
};
console.log(convertUsername('dubá-Solis'));
console.log(convertUsername('joan-of-arc'));
Then, just do User.findOne({ name: convertUsername(req.params.username) })

How do I make sure my getUserProfile cloud function in firebase only returns data if the logged in user's session requests it?

I have a cloud function called getUserProfile that looks like this:
exports.getUserProfile = functions.https.onRequest((request, res) => {
try {
const username = request.body.username || request.query.username;
if (username == null || username.length == 0) return res.send('{"status":"error","errorMessage":"No username Provided."}');
admin.database().ref().child("users").orderByChild("username").equalTo(username).once('value', snapshot => {
if (!snapshot.exists()) return res.send(`{"status":"error","message":"invalid-profile"}`);
snapshot.forEach(function(child) {
const id = child.val().id;
admin.database().ref('users/' + id).once('value', snapshot => {
if (!snapshot.exists()) return res.send('{"status":"error","errorMessage":"user not found."}');
let userData = {}
userData.username = snapshot.val().username || "N/A";
userData.name = snapshot.val().name || "N/A";
userData.email = snapshot.val().email || "N/A";
admin.database().ref('uploads').orderByChild('createdBy').equalTo(id).once('value', snapshot => {
userData.content = {};
const filePromise = [];
snapshot.forEach(function(child) {
let content = {};
content.id = child.key;
content.val = child.val();
content.val.files.forEach(function(fileId) {
filePromise.push(admin.database().ref().child("files").child(fileId.fileId).once("value"));
});
userData.content[child.key] = content;
userData.content[child.key].files = [];
});
Promise.all(filePromise).then(results => {
results.forEach((result => {
const uploadId = result.val().uploadId;
const thumbDownloadURL = result.val().thumbDownloadURL;
userData.content[uploadId].files.push(thumbDownloadURL);
}));
res.send(`{"status":"ok","userdata":${JSON.stringify(userData)}}`);
});
});
});
});
});
} catch (err) {
res.send(`{"status":"error","message":"${err}"}`);
}
});
I use this function to get the user profile's details and render them on the view. However, if someone makes a request let's say from https://reqbin.com/ along with sending {"username": "xxx"} JSON content, the function is still returning the data which is a security breach, and this is an expected behavior. How do I make sure that only the logged-in user gets the data?
You should use JWT authentication to verify users who want to request data from your firebase cloud function. So when a user first signs up a Token is assigned to that user (which can be renewed or revoked when needed), then whenever that user wants to request data they need to send their token by adding Authentication: Bearer + UserJWToken to the request header. Then on the cloud function's side you can verify that token and
here are some links that could help with the subject:
https://firebase.google.com/docs/auth/admin/create-custom-tokens
https://firebase.google.com/docs/auth/admin/verify-id-tokens
https://itnext.io/firebase-cloud-functions-verify-users-tokens-d4e60e314d1a

Firestore rules not picking up custom claims

I can't get custom claims to work in the firestore rules.
I'm using nodeJS (local) to set the custom claims and initialize with the service-account from firebase. The user token is automatically added to the request headers and validates fine on node.
// Initialize
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as admin.ServiceAccount), // Typing is wrong google!
databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`
});
// Add custom claims for additional privileges.
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, {
customClaims })
.then(() => ({ ...decodedToken, customClaims }))
.catch(() => void 0);
if (!payload) { res.status(401).json({ error: 'Error setting custom claims on token' }); return; }
Custom claims object:
// Define custom claims
const customClaims: CustomClaims = {
serverAuth: true,
domain: domainOfUser,
developer: isDeveloper,
admin: isAdmin,
};
Angular Fire 2: User logs in with google redirect then refresh the token:
if (!this.firebaseAuth.auth.currentUser) { return Promise.reject('User object not found in fireAuth service'); }
return this.firebaseAuth.auth.currentUser.getIdToken(true);
When that's al done I do: (the fireAuthService is a custom service that handles some auth stuff)
// On user change
this.fireAuthService.user$.pipe(
map(userAuth => { if (!userAuth) { this.userSource.next(null); } return userAuth; }),
filter(notNullOrUndefined),
switchMap(async userAuth => {
const userDoc = this.userCollection.doc<UserDb>(userAuth.uid);
const exists = await userDoc.get().toPromise().then(user => user.exists)
.catch(() => this.fireAuthService.signOut());
if (!exists) {
const res = await this.serverService.createNewUser(userAuth).catch(() => void 0);
if (!res) { this.fireAuthService.signOut(); }
}
return userAuth;
}),
switchMap(userAuth => this.userCollection.doc<UserDb>(userAuth.uid).valueChanges())
).subscribe(async userDb => {
await this.fireAuthService.getAuthToken();
const isAdmin = await this.fireAuthService
.getTokenPayload()
.then(payload => (payload.claims.customClaims as CustomClaims).admin);
this.userSource.next(new CurrentUser(userDb, this.serverService, isAdmin));
runAngularFire();
});
On the payload are all my custom claims at this point. The firestore calls on the user doc firestore calls are secured by only checking the uid in the firestore rules and this works.
At this point I set up my listeners. They fail with the error:
Missing or insufficient permissions.
The firestore rules are setup as followed:
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to read documents in the user's collection
match /users/{userId} {
allow read: if request.auth.token.sub == userId;
}
// Allow only reads to the db
match /{document=**} {
allow read: if request.auth.token.serverAuth == true;
}
}
I've tried just about anything and I'm at a loss. Any suggestion?
Many thanks in advance!
Edit: I also checked the token send out on channel?database=... This token has the custom claims...
After a night of sleep I noticed my error:
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, { customClaims });
To:
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, customClaims);
Also I did test the rules on a object. Objects probably don't work in rules.

Node.js redirect after use request to POST information

currently I am using Express, Node.js, Graphql to build backend server,
I want to POST data into an online payment system, when I successfully used request to post data, I found that it can console an HTML body in my terminal, but what I want is the Graphql interface can redirect to the platform rather than just output the HTML body, what can I do to solve this problem? Here is my code for the reference.
Mutation: {
createPayment: async (parent, args, { models, user }) => {
const MerchantID = Merchantvalue;
const TotalAmt = await args.TotalAmt;
const ItemName = await args.ItemName;
const ChoosePayment = await args.ChoosePayment;
const PaymentType = payment value;
const TradeDesc = await args.TradeDesc;
const ReturnURL = returnurl;
const EncryptType = encry;
const MerchantTradeNo = await CheckMacValue.random;
const MerchantTradeDate = await CheckMacValue.datetime;
const TheCheckMacValue = await CheckMacValue.PaymentOnceValue(
MerchantID, PaymentType, TotalAmt, TradeDesc,
ItemName, ReturnURL, ChoosePayment, EncryptType,
);
const formData = {
MerchantID: MerchantID,
MerchantTradeNo: MerchantTradeNo,
MerchantTradeDate: MerchantTradeDate,
TotalAmount: TotalAmt,
ItemName: ItemName,
ChoosePayment: ChoosePayment,
PaymentType: PaymentType,
TradeDesc: TradeDesc,
ReturnURL: ReturnURL,
EncryptType: EncryptType,
CheckMacValue: TheCheckMacValue,
};
//In here I can successfully post data, but I want to redirect rather than just console the body in terminal.
request.post(
{ url: 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5', formData: formData },
function optionalCallback (err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log(body);
});
},
},
what you need to do is to pass the right response (code, message) to the client and handle that redirect logic on the client side (web, mobile, etc.)

Resources