[nextjs]SNS Server-51(myapp61+)[signedFetch,getActorData]ย 

๐Ÿ‘‰๐Ÿป ์ด์ „ ํฌ์ŠคํŠธ์—์„œ ์ข‹์•„์š”,๋ถ€์ŠคํŠธ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค.
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.”

Leave a Reply