๐๐ป ์ด์ ํฌ์คํธ์์ ์ข์์,๋ถ์คํธ ๊ธฐ๋ฅ์ ๋ํด์ ์ดํด๋ดค์ต๋๋ค.
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.”