Zod validation for phone numbers - zod

I am new to using zod for valiadtion. I have read through the documentation but I can see anything on how to add a validation for a phone number.
I used the email one to make sure a user enters a correct email, but how do I do the same for a phone number.
Currently it looks like this:
contactNumber: z
.string()
.min(10, { message: 'Must be a valid mobile number' })
.max(14, { message: 'Must be a valid mobile number' }),
I am just not sure this is correct. I have made it a string because I want the user to be able to put a + in front of the number if necessary for example: +27823456787. I don't want the user to be able to put any letters in the number example: 072r536e98, If the user puts letters I want an error message to say "Must be a valid number".
Can anyone maybe advise me on the correct why to do the validation?

You could use a zod refinement to chain together a check from a library that is more capable of making that check. As was mentioned in the comments, the rules are complicated, and specific to the region.
There seems to be a validation from validator.js for example that could handle refining the string to a mobile phone number with isMobilePhone but you may want/need to have the user specify the country they are in.
An example of how this would work might be:
import { z } from "zod";
import validator from "validator";
const schema = z.object({
name: z.string(),
phone: z.string().refine(validator.isMobilePhone)
});
console.log(
schema.safeParse({
name: "test",
phone: "+1-212-456-7890"
})
); // Success
console.log(
schema.safeParse({
name: "test",
phone: "2124567890"
})
); // Success
console.log(
schema.safeParse({
name: "test",
phone: "1234"
})
); // Failure

Related

does redux-query-sync enable sharing url between machines?

I am trying to implement redux-query-sync but the url keeps going to default state if I share the url which has the updated state.
https://github.com/Treora/redux-query-sync/blob/master/src/redux-query-sync.js
I have implemented as shown in the sample - https://codesandbox.io/s/url-query-sync-qjt5f?from-embed=&file=/src/index.js
There is also a PropsRoute implementation in the code.
Your example seems to be working for the string arguments. It's the array param selected which is giving you trouble.
The action creator that you are dispatching here, changeLocation, does not take the right arguments. Look at how you are calling it in your component:
changeLocation({
location: event.target.name,
checked: event.target.checked
});
When it is called from the URL query it is going to be called like:
changeLocation([Stockholm, Oslo]);
So obviously these do not match up. The existing changeLocation action can only handle one location at a time, so there's not a way to map the value from the URL to a payload that you can use with it. You will need to create a separate action.
const setLocation = (payload) => ({ type: "setLocation", payload });
case "setLocation":
return {...state, selected: payload};
My first approach to handle the array values was to implement the optional stringToValue and valueToString settings for the selected param.
This only kinda-sorta works. I can't get it to omit the param from the URL when it is set to the default value. I wonder if it's using a === check? As a hacky solution, I added a filter in the stringToValue to prevent the empty string from getting added to the locations array. But the selected= is always present in the URL.
selected: {
selector: (state) => state.selected,
action: setLocation,
stringToValue: (string) => string.split(",").filter(s => !!s),
valueToString: (value) => value.join(","),
defaultValue: []
}
This really annoyed me, so I followed this bit of advice from the docs:
Note you could equally well put the conversion to and from the string in the selector and action creator, respectively. The defaultValue should then of course be a string too.
And this approach worked much better.
selected: {
selector: (state) => state.selected.join(","),
action: (string) => setLocation(string.split(",")),
defaultValue: ""
}
With those changes you should have a shareable URL.
Forked Sandbox
Thanks Linda. The example I sent was what I referred to do my implementation. It wasn't my work.
Just letting you know that since my app uses PropsRoute I was able to use history props to push the state as params in url and share the url. I had to modify code to use params from url as state if it was available. This worked between tabs. Will be testing across machines.
this.props.history.push("/currenturl/" + state)
this.props.history.push("/currenturl/" + {testobject:{}})
this.props.match.params.testobject
wasn't able to implement redux-query-sync though.

Getting the right permitted fields of conditions

Currently, I'm building an app with with following similar logic:
...
const user = {
isAdmin: true,
company: '5faa6a847b42bf47b8f785a1',
projects: ['5faa6a847b42bf47b8f785a2']
}
function defineAbilityForUser(user) {
return defineAbility((can) => {
if (user.isAdmin) {
can('create', 'ProjectTime', {
company: user.company,
}
);
}
can(
'create',
'ProjectTime',
["company", "project", "user", "start", "end"],
{
company: user.company,
project: {
$in: user.projects
}
}
);
});
}
const userAbility = defineAbilityForUser(user); //
console.log( permittedFieldsOf(userAbility, 'create', 'ProjectTime') );
// console output: ['company', 'project', 'user', 'start', 'end']
Basically an admin should be allowed to create a project time with no field restrictions.
And a none admin user should only be allowed to set the specified fields for projects to which he belongs.
The problem is that I would expect to get [] as output because an admin should be allowed to set all fields for a project time.
The only solution I found was to set all fields on the admin user condition. But this requires a lot of migration work later when new fields are added to the project time model. (also wrapping the second condition in an else-block is not possible in my case)
Is there any other better way to do this? Or maybe, would it be better if the permittedFieldsOf-function would prioritize the condition with no field restrictions?
There is actually no way for casl to know what means all fields in context of your models. It knows almost nothing about their shapes and relies on conditions you provide it to check that objects later. So, it does not have full information.
What you need to do is to pass the 4th argument to override fieldsFrom callback. Check the api docs and reference implementation in #casl/mongoose
In casl v5, that parameter is mandatory. So, this confusion will disappear very soon

Meteor: how to access another user's details?

Meteor.users.findOne() gives me back my user document.
Meteor.users.findOne({_id: 'my ID'}) gives me back my user document.
Meteor.users.findOne({_id: 'another users's ID'}) gives me back UNDEFINED.
This is obviously restricted by security. But how can I access another users's account details e.g. _id, name, profile, etc?
You'll need to add a publisher for the user. Here's an example:
// The user fields we are willing to publish.
const USER_FIELDS = {
username: 1,
emails: 1,
};
Meteor.publish('singleUser', function (userId) {
// Make sure userId is a string.
check(userId, String);
// Publish a single user - make sure only allowed fields are sent.
return Meteor.users.find(userId, { fields: USER_FIELDS });
});
Then on the client you can subscribe like this:
Metor.subscribe('singleUser', userId);
or use a template subscription like this:
this.subscribe('singleUser', userId);
Security notes:
Always check the arguments to your publishers, or clients can do bad things like pass {} for userId. If you get an error, make sure you meteor add check.
Always use a fields option with the users collection. Otherwise you'll publish all of their secrets. See the "Published Secrets" section of common mistakes.
Run it on the server like so:
Server:
Meteor.publish("otherUsers", function (userID) {
return Meteor.users.findOne({_id: userID});
});
Client:
Meteor.subscribe("otherUsers", <userIdYouWantToGetDetailsFor>);
Then you can just do a Meteor.users.findOne on the client keep in mind you can only do it for your user and the userID that you passed in the meteor subscribe

Best way to exclude a Meteor value from the database?

What is the best way to exclude a field value from the data in Meteor when using Autoform, SimpleSchema, Collection2, etc? Say I have:
MySchema = new SimpleSchema({
password: {
type: String,
label: "Enter a password",
min: 8
},
confirmPassword: {
type: String,
label: "Enter the password again",
min: 8,
custom: function () {
if (this.value !== this.field('password').value) {
return "passwordMismatch";
}
}
}
});
... and I do not want to confirmPassword field persisted to the database, what is the best way to handle that? I assume using hooks, but if so where and how? Hopefully there is a way to just exclude one (or more) values without having to redefine the whole whole schema to say which to include and which to exclude. If I have 100 fields and want to exlcude 1, hopefullly the hook or whatever does not need the other 99 defiled too.
TIA
With autoform, you must use a method on the server side. Simply delete the field in the method code when you receive it at the server before inserting the document.

Meteor: insert document upon newly created user

This is probably more simple than I'm making it sound.
I'm allowing my users to create their myprofile when the signin. This is a document that is stored in MyProfile = new Meteor.Collection('myprofile');. The principle is exactly the same as LinkedIn...you login, and you fill out a profile form and any edits you make simply updates that one document.
Within the document there will be fields such as 'summary' 'newsInterest' and others, also 'owner' which is the users Id.
1) How can I insert a document into the MyProfile collection with the 'owner' field being the userId of the newly created user on StartUp?
This is so that the data of this document, the values of these fields will be passed onto the myprofile page. Initially the values returned will be blank but as the user types, upon keyup the myprofile document will be updated.
Users are created as follows on the client. This is fine for now.
2) In addition, please provide any links if people have created users on the server. I called a method to insert the following as an object into Meteor.users.insert(object);but this does not work.
Template.join.events({
'submit #join-form': function(e,t){
e.preventDefault();
Accounts.createUser({
username: t.find('#join-username').value,
password: t.find('#join-password').value,
email: t.find('#join-email').value,
profile:{
fullname: t.find('#join-fullname').value,
summary: [],
newsInterest: [],
otherstuff: []
}
});
Router.go('myprofile');
}
});
1) In order to solve issue one you have two options.
Instead of having a separate collection for profiles like you would in a normalized MySQL database for example. Add the users profile data within the profile object already attached to objects in the user collection. You can then pass in the values you want in the options parameter of the Accounts.createUser function
Template.join.events({
"submit #join-form": function (event) {
event.preventDefault();
var firstName = $('input#firstName').val(),
lastName = $('input#lastName').val(),
username = firstName + '.' + lastName,
email = $('input#email').val(),
password = $('input#password').val(),
profile = {
name: firstName + ' ' + lastName
};
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('myprofile');
}
});
}
});
This is an example using jQuery to get the values but your t.find should work equally fine.
If you really do want to use a separate collection then I recommend using the following code inside the onCreateUser function (server side) instead:
Accounts.onCreateUser(function(options, user) {
user._id = Meteor.users._makeNewID();
profile = options.profile;
profile.userId = user._id;
MyProfile.insert(profile);
return user;
});
When you want to update or add additional data into the profile field for a user you can use the following:
var newProfile = {
summary: 'This summary',
newsInterest: 'This newsInterest',
otherstuff: 'Stuff'
};
Meteor.users.update(Meteor.userId, {$set: {profile: newProfile}});
Or if you went for the separate collection option the following:
var newProfile = MyProfile.findOne(Meteor.userId);
newProfile.summary = 'This summary';
newProfile.newsInterest = 'This newsInterest';
newProfile.otherstuff = 'Stuff';
MyProfile.update(Meteor.userId, newProfile);
Haven't tested this so let my know if I have any syntax / typo errors and I'll update.
Q2: you have to send email, password and profile object to server and use the same Accounts.createUser. Everything works fine.
Question 1 is easily resolved using the Accounts.onCreateUser callback on the server - it provides the user object to the callback, so you can just take the userId from this and insert a new "myProfile" with that owner.
As Will Parker points out below, the user document won't actually have an _id at this point though, so you need to create one and add it to the user object that the callback returns. You can do this with:
user._id = Meteor.users._makeNewID();

Resources