👉🏻 이전 포스트에서 좋아요,부스트 기능에 대해서 살펴봤습니다.
In the previous post, we looked at the “like” and “boost” features.
👉🏻 이번에는 리모트 서버에 좋아요,부스트를 업데이트하기 위해서 사용하는 함수에 대해서 살펴보겠습니다.
Next, we will take a look at the function used to update “likes” and “boosts” on a remote server.
📁 lib/ap.ts
✔️ signedFetch 함수 / function
— 리모트서버에 내 private key로 서명해서 전송하고 응답결과를 받습니다.
I sign the request with my private key, send it to the remote server, and receive the response.
... ...
const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';
const KEY_PATH = path.join(process.cwd(), 'data/keys/private.pem');
const PRIVATE_KEY = fs.readFileSync(KEY_PATH, 'utf8');
... ...
export async function signedFetch(urlStr: string, username: string) {
const actorId = `https://${DOMAIN}/users/${username}`;
const privateKey = getPrivateKey(username);
const url = new URL(urlStr);
const date = new Date().toUTCString();
const signingString = `(request-target): get ${url.pathname}${url.search}\nhost: ${url.host}\ndate: ${date}`;
const signer = crypto.createSign('sha256');
signer.update(signingString);
const signature = signer.sign(privateKey, 'base64');
const keyId = `${actorId}#main-key`;
const sigHeader = `keyId="${keyId}",headers="(request-target) host date",signature="${signature}"`;
console.log(`🔐 Signed GET -> ${urlStr} as ${username}`);
return fetch(urlStr, {
headers: {
'Accept': 'application/activity+json',
'Date': date,
'Signature': sigHeader,
'Host': url.host
}
});
}
1)getPrivateKey()함수 / function
const privateKey = getPrivateKey(username);
A.signedFetch함수에서 사용하는 getPrivateKey함수 입니다.
This is the getPrivateKey function used by the signedFetch function.
B.로그인한 아이디가 user1이면 레거시 경로에서 키파일을 가져옵니다.
If the logged-in ID is user1, retrieve the key file from the legacy path.
(data/keys/private.pem’)
C.다른 사용자인 경우 data/keys/${username}/private.pem 이 경로에서 키파일을 가져옵니다.
For other users, the key file is retrieved from the path: data/keys/${username}/private.pem.
D.키 파일은 회원가입시 자동생성됩니다.
A key file is automatically generated upon registration.
function getPrivateKey(username: string): string {
// data/keys/user1/private.pem 형태 지원
const userKeyPath = path.join(process.cwd(), `data/keys/${username}/private.pem`);
const legacyKeyPath = path.join(process.cwd(), 'data/keys/private.pem');
try {
if (fs.existsSync(userKeyPath)) {
return fs.readFileSync(userKeyPath, 'utf8');
}
// user1이면 legacy 경로도 확인 / Check legacy path if username is user1
if (fs.existsSync(legacyKeyPath)) {
return fs.readFileSync(legacyKeyPath, 'utf8');
}
// data/keys/ 디렉토리에서 직접 / Check directly in data/keys/ directory
const directPath = path.join(process.cwd(), `data/keys/${username}.pem`);
if (fs.existsSync(directPath)) {
return fs.readFileSync(directPath, 'utf8');
}
} catch (e) {
console.error(`🔑 키 로드 실패 / Key load failed for ${username}:`, e);
}
throw new Error(`Private key not found for ${username}`);
}
2) private 키 암호화 / private key encryption
A. 세상에서 유일한 문자열을 sha256이라는 방식으로 만들기
Creating a unique string using the SHA-256 method
const signer = crypto.createSign('sha256');
signer.update(signingString);
B.sha256으로 만들면 16진수 바이너리 형태가 됨 이걸 RSA로 암호화하고 다시 문자열로 변환
Generating a hash using SHA-256 results in a hexadecimal binary format; this is then encrypted via RSA and converted back into a string.
const signature = signer.sign(privateKey, 'base64');
3) 조립된 정보를 리모트 서버에 전송하고 리모트 서버에서 받은 응답 결과를 리턴
Transmit the assembled information to a remote server and return the response received from that server.
return fetch(urlStr, {
headers: {
'Accept': 'application/activity+json',
'Date': date,
'Signature': sigHeader,
'Host': url.host
}
});
A.응답결과를 좋아요나 부스트 코드에서 아래와 같이 사용됨.
The response result is used as shown below in the code for “likes” or “boosts.”
a)private키로 서명해서 리모트서버에 전송하면 리모트 서버에서 내 public key를 호출함.
When you sign with your private key and send it to a remote server, the remote server retrieves your public key.
b)리모트 서버에서 내 private키와 public키가 일치하면 pr.ok = true를 리턴해 줌
If my private key and public key match on the remote server, it returns pr.ok = true.
const pr = await signedFetch(target, username);
if (pr.ok) { ... }
B.반대로 내 서버에서 리모트 서버에 public key요청하는 부분 코드는 아래와 같음
Conversely, the code on my server that requests a public key from the remote server is as follows:
# app/users/[username]/inbox/route.ts
... ...
const { isValid } = await fetchActorPublicKeyWithRetry(
actorIdForVerify,
username,
verifyReq
);
... ...
✔️ getActorData 함수 / function
— signedFetch의 응답 데이터를 그대로 활용하는 함수
A function that utilizes the response data from signedFetch as-is
export async function getActorData(actorUrl: string, username: string) {
const res = await signedFetch(actorUrl, username);
if (!res.ok) throw new Error(`getActorData failed: ${res.status} ${await res.text()}`);
const data = await res.json();
return { id: data.id, inbox: data.inbox, data };
}
마태복음 21장 22절 / Matthew 21: 22
“너희가 기도할 때에 무엇이든지 믿고 구하는 것은 다 받으리라 하시니라”
“And everything you ask in prayer, believing, you will receive.”