[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