NIP-05 Verification

This commit is contained in:
KoalaSat 2023-01-23 17:48:33 +01:00
parent 0b43f686d0
commit 7c5c65534b
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
14 changed files with 262 additions and 57 deletions

View File

@ -4,11 +4,17 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.UUID;
@ -129,6 +135,29 @@ public class Event {
return filtered;
}
protected boolean validateNip05(String nip05) {
String[] parts = nip05.split("@");
if (parts.length < 1) return false;
String name = parts[0];
String domain = parts[1];
if (!name.matches("^[a-z0-9-_]+$")) return false;
try {
String url = "https://" + domain + "/.well-known/nostr.json?name=" + name;
JSONObject response = getJSONObjectFromURL(url);
JSONObject names = response.getJSONObject("names");
String key = names.getString(name);
return key.equals(pubkey);
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return false;
}
protected void saveNote(SQLiteDatabase database, String userPubKey) {
ContentValues values = new ContentValues();
values.put("id", id);
@ -203,21 +232,27 @@ public class Event {
protected void saveUserMeta(SQLiteDatabase database) throws JSONException {
JSONObject userContent = new JSONObject(content);
String query = "SELECT created_at FROM nostros_users WHERE id = ?";
String query = "SELECT created_at, valid_nip05, nip05 FROM nostros_users WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {pubkey});
String nip05 = userContent.optString("nip05");
ContentValues values = new ContentValues();
values.put("name", userContent.optString("name"));
values.put("picture", userContent.optString("picture"));
values.put("about", userContent.optString("about"));
values.put("lnurl", userContent.optString("lud06"));
values.put("nip05", userContent.optString("nip05"));
values.put("nip05", nip05);
values.put("main_relay", userContent.optString("main_relay"));
values.put("created_at", created_at);
if (cursor.getCount() == 0) {
values.put("id", pubkey);
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
database.insert("nostros_users", null, values);
} else if (cursor.moveToFirst() && created_at > cursor.getInt(0)) {
if (cursor.getInt(1) == 0 || !cursor.getString(2).equals(nip05)) {
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
}
String whereClause = "id = ?";
String[] whereArgs = new String[] {
this.pubkey
@ -227,23 +262,23 @@ public class Event {
}
protected void savePets(SQLiteDatabase database) throws JSONException {
for (int i = 0; i < tags.length(); ++i) {
JSONArray tag = tags.getJSONArray(i);
String petId = tag.getString(1);
String name = "";
if (tag.length() >= 4) {
name = tag.getString(3);
}
String query = "SELECT * FROM nostros_users WHERE id = ?";
Cursor cursor = database.rawQuery(query, new String[] {petId});
if (cursor.getCount() == 0) {
ContentValues values = new ContentValues();
values.put("id", petId);
values.put("name", name);
values.put("contact", true);
database.insert("nostros_users", null, values);
}
for (int i = 0; i < tags.length(); ++i) {
JSONArray tag = tags.getJSONArray(i);
String petId = tag.getString(1);
String name = "";
if (tag.length() >= 4) {
name = tag.getString(3);
}
String query = "SELECT * FROM nostros_users WHERE id = ?";
Cursor cursor = database.rawQuery(query, new String[] {petId});
if (cursor.getCount() == 0) {
ContentValues values = new ContentValues();
values.put("id", petId);
values.put("name", name);
values.put("contact", true);
database.insert("nostros_users", null, values);
}
}
}
protected void saveFollower(SQLiteDatabase database, String userPubKey) throws JSONException {
@ -270,4 +305,38 @@ public class Event {
}
}
}
protected static JSONObject getJSONObjectFromURL(String urlString) throws JSONException, IOException {
HttpURLConnection urlConnection = null;
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.setDoOutput(true);
urlConnection.connect();
BufferedReader br=new BufferedReader(new InputStreamReader(url.openStream()));
String jsonString;
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line+"\n");
}
br.close();
jsonString = sb.toString();
System.out.println("JSON: " + jsonString);
urlConnection.disconnect();
return new JSONObject(jsonString);
}
}

View File

@ -97,6 +97,9 @@ public class DatabaseModule {
try {
database.execSQL("ALTER TABLE nostros_users ADD COLUMN nip05 TEXT;");
} catch (SQLException e) { }
try {
database.execSQL("ALTER TABLE nostros_users ADD COLUMN valid_nip05 BOOLEAN DEFAULT FALSE;");
} catch (SQLException e) { }
}
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {

View File

@ -79,7 +79,19 @@ export const MenuItems: React.FC = () => {
/>
</View>
<View>
<Text variant='titleMedium'>{user?.name}</Text>
<View style={styles.username}>
<Text variant='titleMedium'>{user?.name}</Text>
{user?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
<Text>{formatPubKey(publicKey ?? '')}</Text>
</View>
</View>
@ -137,14 +149,6 @@ export const MenuItems: React.FC = () => {
)
}
/>
{/* <Drawer.Item
label={t('menuItems.configuration')}
icon='cog-outline'
key='config'
active={drawerItemIndex === 1}
onPress={() => onPressItem('config', 1)}
onTouchEnd={() => setDrawerItemIndex(-1)}
/> */}
</Drawer.Section>
)}
<Drawer.Section showDivider={false}>
@ -213,6 +217,13 @@ const styles = StyleSheet.create({
borderBottomRightRadius: 28,
padding: 24,
},
username: {
flexDirection: 'row',
},
verifyIcon: {
paddingTop: 6,
paddingLeft: 5,
},
})
export default MenuItems

View File

@ -100,8 +100,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
{note.reply_event_id && showAnswerData && (
<TouchableRipple
onPress={() =>
note.kind !== Kind.RecommendRelay &&
push('Note', { noteId: note.reply_event_id })
note.kind !== Kind.RecommendRelay && push('Note', { noteId: note.reply_event_id })
}
>
<Card.Content style={[styles.answerContent, { borderColor: theme.colors.onSecondary }]}>
@ -200,7 +199,19 @@ export const NoteCard: React.FC<NoteCardProps> = ({
/>
</View>
<View style={styles.titleUserInfo}>
<Text style={styles.titleUsername}>{usernamePubKey(note.name, nPub)}</Text>
<View style={styles.titleUser}>
<Text style={styles.titleUsername}>{usernamePubKey(note.name, nPub)}</Text>
{note?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
<Text>{timestamp}</Text>
</View>
</View>
@ -223,9 +234,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
color={theme.colors.onPrimaryContainer}
/>
)}
onPress={() =>
note.kind !== Kind.RecommendRelay && push('Note', { noteId: note.id })
}
onPress={() => note.kind !== Kind.RecommendRelay && push('Note', { noteId: note.id })}
>
{repliesCount}
</Button>
@ -319,6 +328,10 @@ const styles = StyleSheet.create({
link: {
textDecorationLine: 'underline',
},
verifyIcon: {
paddingTop: 3,
paddingLeft: 5,
},
})
export default NoteCard

View File

@ -88,9 +88,20 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ userPubKey, bottomShee
/>
</View>
<View>
<View style={styles.username}>
<Text variant='titleMedium'>{username}</Text>
{/* <MaterialCommunityIcons name="check-decagram-outline" size={16} color={theme.colors.onPrimaryContainer} /> */}
<View style={styles.usernameData}>
<View style={styles.username}>
<Text variant='titleMedium'>{username}</Text>
{user?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
<Text>{user?.nip05}</Text>
</View>
</View>
@ -184,9 +195,12 @@ const styles = StyleSheet.create({
snackbar: {
marginBottom: 85,
},
username: {
usernameData: {
paddingLeft: 16,
},
username: {
flexDirection: 'row',
},
contacts: {
flexDirection: 'row',
alignItems: 'center',
@ -224,6 +238,10 @@ const styles = StyleSheet.create({
cardUser: {
flex: 1,
},
verifyIcon: {
paddingTop: 6,
paddingLeft: 5,
},
})
export default ProfileCard

View File

@ -8,6 +8,7 @@ export interface Note extends Event {
lnurl: string
reply_event_id: string
user_created_at: number
valid_nip05: boolean
}
const databaseToEntity: (object: any) => Note = (object = {}) => {
@ -22,7 +23,7 @@ export const getMainNotes: (
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE (nostros_users.contact = 1 OR nostros_notes.pubkey = '${pubKey}')
@ -45,7 +46,7 @@ export const getMentionNotes: (
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE (nostros_notes.reply_event_id IN (
@ -114,7 +115,7 @@ export const getNotes: (
) => Promise<Note[]> = async (db, { filters = {}, limit, contacts, includeIds }) => {
let notesQuery = `
SELECT
nostros_notes.*, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
`

View File

@ -12,6 +12,7 @@ export interface User {
lnurl?: string
nip05?: string
created_at?: number
valid_nip05?: boolean
}
const databaseToEntity: (object: object) => User = (object) => {

View File

@ -171,8 +171,20 @@ export const ContactsFeed: React.FC = () => {
lud06={item.lnurl}
size={40}
/>
<View style={styles.contactName}>
<Text>{formatPubKey(nPub)}</Text>
<View style={styles.contactData}>
<View style={styles.contactName}>
<Text>{formatPubKey(nPub)}</Text>
{item.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
{item.name && <Text variant='titleSmall'>{username(item)}</Text>}
</View>
</View>
@ -426,9 +438,12 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
width: '100%',
},
contactName: {
contactData: {
paddingLeft: 16,
},
contactName: {
flexDirection: 'row',
},
contactInfo: {
flexDirection: 'row',
alignContent: 'center',
@ -456,6 +471,10 @@ const styles = StyleSheet.create({
list: {
paddingBottom: 80,
},
verifyIcon: {
paddingTop: 4,
paddingLeft: 5,
},
})
export default ContactsFeed

View File

@ -125,10 +125,20 @@ export const ConversationsFeed: React.FC = () => {
/>
<View style={styles.contactName}>
<Text variant='titleSmall'>{userMame}</Text>
{user?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
</View>
<View style={styles.contactInfo}>
<View style={styles.contactName}>
<View style={styles.contactDate}>
<Text>{moment.unix(item.created_at).format('L HH:mm')}</Text>
{item.pubkey !== publicKey && !item.read && <Badge size={16}></Badge>}
</View>
@ -213,8 +223,20 @@ export const ConversationsFeed: React.FC = () => {
lud06={item.lnurl}
size={40}
/>
<View style={styles.contactName}>
<Text variant='titleSmall'>{formatPubKey(item.id)}</Text>
<View style={styles.contactData}>
<View style={styles.contactName}>
<Text variant='titleSmall'>{formatPubKey(item.id)}</Text>
{item?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
{item.name && <Text variant='titleSmall'>{username(item)}</Text>}
</View>
</View>
@ -346,9 +368,19 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
width: '100%',
},
contactName: {
contactData: {
paddingLeft: 16,
},
contactDate: {
paddingLeft: 16,
},
contactName: {
flexDirection: 'row',
alignContent: 'center',
justifyContent: 'center',
paddingLeft: 16,
paddingTop: 10,
},
contactUser: {
flexDirection: 'row',
alignContent: 'center',
@ -378,6 +410,10 @@ const styles = StyleSheet.create({
list: {
paddingBottom: 64,
},
verifyIcon: {
paddingTop: 3,
paddingLeft: 5,
},
})
export default ConversationsFeed

View File

@ -98,7 +98,7 @@ export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
relayPool?.subscribe('homepage-contacts-meta', [
{
kinds: [Kind.Metadata],
authors: users.map((user) => user.id),
authors,
},
])
setRefreshing(false)

View File

@ -202,7 +202,19 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
/>
</View>
<View style={styles.titleUserData}>
<Text style={styles.titleUsername}>{usernamePubKey(note.name, nPub)}</Text>
<View style={styles.titleUser}>
<Text style={styles.titleUsername}>{usernamePubKey(note.name, nPub)}</Text>
{note?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
<Text>{timestamp}</Text>
</View>
</View>
@ -214,8 +226,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
{note.reply_event_id && (
<TouchableRipple
onPress={() =>
note.kind !== Kind.RecommendRelay &&
push('Note', { noteId: note.reply_event_id })
note.kind !== Kind.RecommendRelay && push('Note', { noteId: note.reply_event_id })
}
>
<View style={[styles.answerContent, { borderColor: theme.colors.onSecondary }]}>
@ -420,6 +431,10 @@ const styles = StyleSheet.create({
link: {
textDecorationLine: 'underline',
},
verifyIcon: {
paddingTop: 3,
paddingLeft: 5,
},
})
export default NotePage

View File

@ -286,7 +286,7 @@ export const ProfileConfigPage: React.FC = () => {
</View>
<View style={styles.actionButton}>
<IconButton
icon='check-circle-outline'
icon='check-decagram-outline'
size={28}
onPress={() => bottomSheetNip05Ref.current?.open()}
/>
@ -426,7 +426,6 @@ export const ProfileConfigPage: React.FC = () => {
<Text variant='titleLarge'>{t('profileConfigPage.nip05Title')}</Text>
<Text variant='bodyMedium'>
{t('profileConfigPage.nip05Description')}
<Text> </Text>
<Text
style={styles.link}
onPress={async () =>

View File

@ -34,6 +34,7 @@ import ProfileCard from '../../Components/ProfileCard'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import { getNpub } from '../../lib/nostr/Nip19'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
interface ProfilePageProps {
route: { params: { pubKey: string } }
@ -208,9 +209,20 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
/>
</View>
<View>
<View style={styles.userName}>
<Text variant='titleMedium'>{user && username(user)}</Text>
{/* <MaterialCommunityIcons name="check-decagram-outline" size={16} color={theme.colors.onPrimaryContainer} /> */}
<View style={styles.userData}>
<View style={styles.userName}>
<Text variant='titleMedium'>{user && username(user)}</Text>
{user?.valid_nip05 ? (
<MaterialCommunityIcons
name='check-decagram-outline'
size={14}
color={theme.colors.onPrimaryContainer}
style={styles.verifyIcon}
/>
) : (
<></>
)}
</View>
<Text>{user?.nip05}</Text>
</View>
</View>
@ -322,9 +334,12 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
userName: {
userData: {
paddingLeft: 16,
},
userName: {
flexDirection: 'row',
},
actionButton: {
justifyContent: 'center',
alignItems: 'center',
@ -340,6 +355,10 @@ const styles = StyleSheet.create({
noteCard: {
marginBottom: 16,
},
verifyIcon: {
paddingTop: 6,
paddingLeft: 5,
},
})
export default ProfilePage

View File

@ -90,6 +90,7 @@ class RelayPool {
public readonly unsubscribeAll: () => void = async () => {
this.unsubscribe(Object.keys(this.subscriptions))
this.subscriptions = {}
}
}