-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
144 lines (134 loc) · 4.19 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
const { TwitterApi } = require('twitter-api-v2');
const { ArweaveClient } = require('ar-wrapper');
const { keccak256 } = require('@ethersproject/keccak256');
const { toUtf8Bytes } = require('@ethersproject/strings');
const { randomBytes } = require('crypto');
const DEFAULT_OPTIONS = {
projectName: 'verify_user',
twitterMessage: 'I am verifying my Twitter'
}
class VerifyUserClient {
twitterClient;
arweaveClient;
options;
constructor(twitterConfig, adminAddress, arweaveKeyfile, options = DEFAULT_OPTIONS) {
this.twitterClient = new TwitterApi(twitterConfig.bearer_token);
this.arweaveClient = new ArweaveClient(adminAddress, arweaveKeyfile);
this.options = options;
}
// get hash for verification
// optionally generated client side
createTwitterVerificationHash(signature) {
const salt = randomBytes(32).toString();
const hash = keccak256(toUtf8Bytes([signature, salt].join('-')));
return {
status: 'Success',
msg: 'success',
hash
}
}
// returns a message that is ready to sign with twitter userID embedded inside
// optionally generated on client-side
async generateMessageToSign(handle, messageTemplate = "Please sign to verify you own this address (gassless).") {
if (!handle) {
return {
status: 'Error',
msg: "error: handle is required"
}
}
try {
const { data: { id } } = await this.twitterClient.v2.userByUsername(handle);
return {
status: 'Success',
msg: "success",
messageToSign: `${messageTemplate} userId: ${id}`,
userId: id,
}
} catch (err) {
return {
status: 'Error',
msg: `error: internal error retrieving user id, ${err}`,
}
}
}
// twitter handle and verification hash required, no address stored
// verification hash and handle are stored
async verifyTwitter(handle, verificationHash) {
const tweetTemplate = `${this.options.twitterMessage}`
try {
const { data: { id: userId } } = await this.twitterClient.v2.userByUsername(handle);
const { data: tweets } = await this.twitterClient.v2.userTimeline(userId, { exclude: "replies", max_results: 5 });
for (const tweet of tweets.data) {
if (tweet.text.startsWith(tweetTemplate) && (tweet.text.includes(verificationHash))) {
return {
status: 'Success',
msg: `succesfully verified twitter, tweetId: ${tweet.id}`,
}
}
}
return {
status: 'Error',
msg: `Could not find tweet of the form ${tweetTemplate} and ${verificationHash} from @${handle} (${userId})`
}
} catch (err) {
return {
status: 'Error',
msg: `${err}`
}
}
}
// store signature and name
// username should not be traceable back to twitter
// signedMessage to verify user owns account
async storeSignature(signedMessage, username) {
if (!signedMessage) {
return {
status: 'Error',
msg: 'error, missing required fields'
}
}
const SIG_DOC = `${this.options.projectName}_signature`;
const DOC_TYPE = `${this.options.projectName}_doc_type`;
const tags = {};
tags[DOC_TYPE] = 'signature';
tags['username'] = username;
tags['signedMessageHash'] = keccak256(toUtf8Bytes(signedMessage));
const doc = await this.arweaveClient.addDocument(SIG_DOC, keccak256(toUtf8Bytes(signedMessage)), tags);
if (doc.posted) {
return {
status: 'Success',
msg: 'success',
username,
};
} else {
return {
status: 'Error',
msg: 'error adding signature'
}
}
}
// get signature from arweave
async getUser(signedMessage) {
const tags = {
signedMessageHash: keccak256(toUtf8Bytes(signedMessage)),
};
tags[`${this.options.projectName}_doc_type`] = 'signature';
const sigDoc = await this.arweaveClient.getDocumentsByTags(tags)
if (sigDoc.length > 0) {
return {
status: 'Success',
msg: 'success',
username: sigDoc[0].tags['username'],
}
} else {
return {
status: 'Error',
msg: "error couldn't find user",
};
}
}
}
module.exports = {
VerifyUserClient,
DEFAULT_OPTIONS,
}