Add encrypted key support for chat and nodes
Introduces encrypted private key storage for nodes and users, updates chat message schema to support sender-side encryption, and adds supporting libraries and tests for cryptographic signing and identity unlock flows. Includes new database migrations, API route updates, and React components for identity unlock prompts.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Signed Fetch - Client-side API wrapper
|
||||
*
|
||||
* Automatically signs all user actions with their private key before
|
||||
* sending to the server. This ensures cryptographic proof of authenticity.
|
||||
*/
|
||||
|
||||
import { createSignedAction, hasUserPrivateKey } from '@/lib/crypto/user-signing';
|
||||
|
||||
export interface SignedFetchOptions {
|
||||
method?: string;
|
||||
body?: any;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a signed API request
|
||||
*
|
||||
* @param url - The API endpoint
|
||||
* @param action - The action being performed (e.g., 'like', 'follow', 'post')
|
||||
* @param data - The action data
|
||||
* @param userDid - The user's DID
|
||||
* @param userHandle - The user's handle
|
||||
* @param options - Additional fetch options
|
||||
*/
|
||||
export async function signedFetch(
|
||||
url: string,
|
||||
action: string,
|
||||
data: any,
|
||||
userDid: string,
|
||||
userHandle: string,
|
||||
options: SignedFetchOptions = {}
|
||||
): Promise<Response> {
|
||||
// Check if user has their private key loaded
|
||||
if (!hasUserPrivateKey()) {
|
||||
throw new Error('User identity not unlocked. Please log in again.');
|
||||
}
|
||||
|
||||
// Create signed action
|
||||
// Note: createSignedAction now generates nonce and ts internally
|
||||
const signedAction = await createSignedAction(action, data, userDid, userHandle);
|
||||
|
||||
// Make the request
|
||||
return fetch(url, {
|
||||
method: options.method || 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
body: JSON.stringify(signedAction),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for common actions
|
||||
*/
|
||||
export const signedAPI = {
|
||||
/**
|
||||
* Like a post
|
||||
*/
|
||||
async likePost(postId: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/posts/${postId}/like`,
|
||||
'like',
|
||||
{ postId },
|
||||
userDid,
|
||||
userHandle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlike a post
|
||||
*/
|
||||
async unlikePost(postId: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/posts/${postId}/like`,
|
||||
'unlike',
|
||||
{ postId },
|
||||
userDid,
|
||||
userHandle,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Follow a user
|
||||
*/
|
||||
async followUser(targetHandle: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/users/${targetHandle}/follow`,
|
||||
'follow',
|
||||
{ targetHandle },
|
||||
userDid,
|
||||
userHandle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unfollow a user
|
||||
*/
|
||||
async unfollowUser(targetHandle: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/users/${targetHandle}/follow`,
|
||||
'unfollow',
|
||||
{ targetHandle },
|
||||
userDid,
|
||||
userHandle,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a post
|
||||
*/
|
||||
async createPost(
|
||||
content: string,
|
||||
mediaIds: string[],
|
||||
linkPreview: any,
|
||||
replyToId: string | undefined,
|
||||
isNsfw: boolean,
|
||||
userDid: string,
|
||||
userHandle: string
|
||||
) {
|
||||
return signedFetch(
|
||||
'/api/posts',
|
||||
'post',
|
||||
{ content, mediaIds, linkPreview, replyToId, isNsfw },
|
||||
userDid,
|
||||
userHandle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Repost a post
|
||||
*/
|
||||
async repostPost(postId: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/posts/${postId}/repost`,
|
||||
'repost',
|
||||
{ postId },
|
||||
userDid,
|
||||
userHandle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unrepost a post
|
||||
*/
|
||||
async unrepostPost(postId: string, userDid: string, userHandle: string) {
|
||||
return signedFetch(
|
||||
`/api/posts/${postId}/repost`,
|
||||
'unrepost',
|
||||
{ postId },
|
||||
userDid,
|
||||
userHandle,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user