mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
NIP-05 Verification
This commit is contained in:
parent
0b43f686d0
commit
7c5c65534b
@ -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
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -79,7 +79,19 @@ export const MenuItems: React.FC = () => {
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<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
|
||||
|
@ -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}>
|
||||
<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
|
||||
|
@ -88,9 +88,20 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ userPubKey, bottomShee
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<View style={styles.usernameData}>
|
||||
<View style={styles.username}>
|
||||
<Text variant='titleMedium'>{username}</Text>
|
||||
{/* <MaterialCommunityIcons name="check-decagram-outline" size={16} color={theme.colors.onPrimaryContainer} /> */}
|
||||
{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
|
||||
|
@ -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
|
||||
`
|
||||
|
@ -12,6 +12,7 @@ export interface User {
|
||||
lnurl?: string
|
||||
nip05?: string
|
||||
created_at?: number
|
||||
valid_nip05?: boolean
|
||||
}
|
||||
|
||||
const databaseToEntity: (object: object) => User = (object) => {
|
||||
|
@ -171,8 +171,20 @@ export const ContactsFeed: React.FC = () => {
|
||||
lud06={item.lnurl}
|
||||
size={40}
|
||||
/>
|
||||
<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
|
||||
|
@ -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.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
|
||||
|
@ -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)
|
||||
|
@ -202,7 +202,19 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.titleUserData}>
|
||||
<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
|
||||
|
@ -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 () =>
|
||||
|
@ -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.userData}>
|
||||
<View style={styles.userName}>
|
||||
<Text variant='titleMedium'>{user && username(user)}</Text>
|
||||
{/* <MaterialCommunityIcons name="check-decagram-outline" size={16} color={theme.colors.onPrimaryContainer} /> */}
|
||||
{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
|
||||
|
@ -90,6 +90,7 @@ class RelayPool {
|
||||
|
||||
public readonly unsubscribeAll: () => void = async () => {
|
||||
this.unsubscribe(Object.keys(this.subscriptions))
|
||||
this.subscriptions = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user