I am trying to sign typed data via MetaMask "eth_signTypedData_v4". Recovering signer via recoverTypedSignature_v4 returns correct address that I signed my data with. But smart contract returns wrong address every time as ecrecover output. Can't understand what is wrong with the code.
js:
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();// 签名
const signerAddress = (await signer.getAddress()).toLowerCase();
const originalMessage = {
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
Greeting: [
{ name: 'contents', type: 'string' },
{ name: "sender", type: "address" },
{ name: "x", type: "uint256" }
],
},
primaryType: 'Greeting',
domain: {
name: 'SignatureVerifyTest',
version: '1',
chainId: 31337,
verifyingContract: "0x5fbdb2315678afecb367f032d93f642f64180aa3"
},
message: {
contents: 'Hello',
sender: signerAddress,
x: 123
}
};
const signedMessage = await signer.provider.send("eth_signTypedData_v4", [signerAddress, JSON.stringify(originalMessage)]);
const { v, r, s } = ethers.utils.splitSignature(signedMessage);
console.log('signerAddress:', signerAddress)
console.log("r:", r);
console.log("s:", s);
console.log("v:", v);
bytes32 eip712DomainHash = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("SignatureVerifyTest")),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
bytes32 hashStruct = keccak256(
abi.encode(keccak256("Greeting(string contents,address sender,uint256 x)"), contents, sender, x)
);
bytes32 hash = ECDSA.toTypedDataHash(eip712DomainHash, hashStruct);
address signer = ECDSA.recover(hash, v, r, s);
console.log("sender", sender);
console.log("signer", signer);
I've a simple user model and i want to exclude password from it. Using the official docs and answer here i've tried to make it work but this doesn't seem to work as i get a response something like this.
[
{
"$__": {
"strictMode": true,
"selected": {},
"getters": {},
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
19,
73,
179,
3,
138,
216,
246,
182,
234,
62,
37
]
}
},
"wasPopulated": false,
"activePaths": {
"paths": {
"password": "init",
"email": "init",
"name": "init",
"_id": "init",
"__v": "init"
},
"states": {
"ignore": {},
"default": {},
"init": {
"_id": true,
"name": true,
"email": true,
"password": true,
"__v": true
},
"modify": {},
"require": {}
},
"stateNames": [
"require",
"modify",
"init",
"default",
"ignore"
]
},
"pathsToScopes": {},
"cachedRequired": {},
"session": null,
"$setCalled": [],
"emitter": {
"_events": {},
"_eventsCount": 0,
"_maxListeners": 0
},
"$options": {
"skipId": true,
"isNew": false,
"willInit": true
}
},
"isNew": false,
"_doc": {
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
19,
73,
179,
3,
138,
216,
246,
182,
234,
62,
37
]
}
},
"name": "Kamran",
"email": "kamran#example.com",
"password": "Pass1234",
"__v": 0
},
"$locals": {},
"$init": true
}
]
Here's my model. I'm using Typegoose but the same is the case with Mongoose as well.
export class User extends Typegoose {
#Transform((value) => value.toString(), { toPlainOnly: true })
_id: string;
#prop({ required: true })
public name!: string;
#prop({ required: true })
public email!: string;
#Exclude({ toPlainOnly: true })
#prop({ required: true })
public password!: string;
}
My user service
#Injectable()
export class UserService {
constructor(#InjectModel(User) private readonly user: ReturnModelType<typeof User>) {}
async getUsers() {
return this.user.find().exec();
}
}
and user controller
#Controller('users')
#UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly userService: UserService) {}
#Get()
async index() : Promise<User[] | []> {
return this.userService.getUsers();
}
}
I tried to use my custom interceptor as described here but that didn't work so i changed it to below code as given here
#Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => classToPlain(this.transform(data))));
}
transform(data) {
const transformObject = (obj) => {
const result = obj.toObject();
const classProto = Object.getPrototypeOf(new User());
Object.setPrototypeOf(result, classProto);
return result;
}
return Array.isArray(data) ? data.map(obj => transformObject(obj)) : transformObject(data);
}
}
Now it's working but the code is not generic. Any way to make it generic?
I think i've identified the problem but not sure why this happens yet. So here's the problem if i return the instance of the class then the serialization works but if i just return plain db response then the above mentioned issue occurs. So what i did is i updated the prototype of the response objects in the transform method of toObject to my user class. Here's the code.
User Model
#modelOptions({
schemaOptions: {
toObject: {
transform: function(doc, ret, options) {
Object.setPrototypeOf(ret, Object.getPrototypeOf(new User()));
}
},
},
})
export class User {
#Transform((value) => value.toString(), { toPlainOnly: true })
public _id: string;
#prop({ required: true })
public name!: string;
#prop({ required: true })
public email!: string;
#Exclude({ toPlainOnly: true })
#prop({ required: true })
public password!: string;
}
TransformInterceptor
#Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => classToPlain(this.transform(data))));
}
transform(data) {
return Array.isArray(data) ? data.map(obj => obj.toObject()) : data.toObject();
}
}
And now if you just decorate your controller or method with #UseInterceptors(TransformInterceptor) it will work perfectly. This is a typegoose solution but it will work the same way with mongoose as well.
#kamran-arshad answer helped me find an appropriate way to accomplish the expected result with typegoose. You can use the decorator #modelOptions() and pass it an object with a function to generate the JSON.
#modelOptions({
toJSON: {
transform: function(doc, ret, options) {
delete ret.password;
return ret;
}
}
})
export class User extends Typegoose {
#prop({required: true})
name!: string;
#prop({required: true})
password!: string;
}
It is not perfect, as decorators from the class-transform do not work as expected, but it gets the job done. Also, you should avoid using the ClassSerializerInterceptor because it will give the same result that OP mentioned.
To avoid any back-pain and headaches with Mongoose,
I would suggest using the plainToClass to have a full mongoose/class-transform compatibility and avoid having to make custom overrides to overcome this isse.
Example, add this in your service :
async validateUser(email: string, password: string): Promise<UserWithoutPassword | null> {
const user = await this.usersService.findOne({ email });
if (user && await compare(password, user.password))
{
return plainToClass(UserWithoutPassword, user.toObject());
}
return null;
}
This way you can use the #Exclude() and other decorators
Source : Stackoverflow answer
Here is my implementation, all the Decorators will work without needing the ClassSerializerInterceptor
PersonSchema.methods.toJSON = function () {
return plainToClass(Person, this.toObject());
};
import { Exclude, Expose } from "class-transformer";
export class UserSerializer {
#Expose()
email: string;
#Expose()
fullName: string;
#Exclude()
password: string;
#Expose()
username: string;
}
#Post("new")
async createNewAccount(#Body() body: CreateUserDTO) {
return plainToClass(UserSerializer, await (await this.authService.createNewUser(body)).toJSON())
}
The solution that worked for me: #Transform(({value}) => value.toHexString(), {toPlainOnly: true})
Full code example:
export class User {
#ObjectIdColumn()
#Transform(({value}) => value.toHexString(), {toPlainOnly: true})
_id: ObjectID
#Column({unique: true })
username: string
#Column({ unique: true })
email: string
#Exclude({ toPlainOnly: true })
#Column()
password: string
}
Getting error unknown top level operator $regex
search.vue `
let questDocuments = await conversation
.find({ query: { $limit: 100, $search: q, skippop: true } })
.then(response => {`
q is the string being passed
service hook
before: {
all: [],
find: [
hookBeforeFind,
search({
fields: ["label"],
deep: true
})
],
Model
const conversation = new Schema(
{
label: { type: String, required: true },
nodeId: { type: String, required: true },
details: { type: String },
url: { type: String },
creator: { type: String },
handle: { type: String },
date: { type: String },
From search bar add expression to search. E.g "the"
Add $regex to the whitelist option of the Mongoose service:
app.use('/messages', service({
Model,
whitelist: [ '$regex' ]
}));
try this
// regex to find records that start with letter any name , example "e"
Model.aggregate([
{
$match: {
field_name: {
$regex: "^" + searchName,
$options: "i"
}
}
}]).exec(function(err, result) {
if (err) { // handle here }
if (result) { // do something }
}
I have object like this:
data() {
return {
headings: ['id','remark'],
rows: [
{
id: 1,
FirstName: 'Jhon',
LastName: 'Doe',
remark: 0
},
{
id: 2,
FirstName: 'Foo',
LastName: 'Bar',
remark: 1
}
]
}}
Is there away how do i check if key headings object exist in rows object.
Thanks and sorry for my bad english.
Yes, There's a way
function (row) {
return this.headings.reduce((res, heading) => res &= row.hasOwnProperty(heading), true)
}
This method should return true - if the row has the heading.
What I have is an object like this:
formData = {
name: {
value: '',
valid: true
},
zip: {
value: 'ff',
valid: false
},
//...
}
And I want to filter this so that I only have the invalid objects. The problem with _.where and _.filter is that it returns an object like this:
[
0: {
value: '',
valid: false
},
1: {
value: '',
valid: false
}
]
I need the parent key names, name and zip to be included. How do I do this?
You are looking for _.pick() my friend.
https://lodash.com/docs#pick
_.pick(formData, function(value) {
return value.valid;
})
Good luck! :)