[nextjs]SNS Server-21(myapp31)Β 

πŸ‘‰πŸ» νšŒμ›κ°€μž… 및 둜그인 κΈ°λŠ₯을 μ‹œμž‘ν•©λ‹ˆλ‹€.
We are launching the sign-up and login functions.

πŸ‘‰πŸ» publick key κ²€μ¦λ‘œμ§μ„ μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.(기쑴은 검증 없이 패슀)
Added public key verification logic (previously, it proceeded without verification).

πŸ‘‰πŸ» Userν…Œμ΄λΈ” ꡬ쑰λ₯Ό λ³€κ²½ ν–ˆμŠ΅λ‹ˆλ‹€.
The User table structure has been modified.

πŸ‘‰πŸ» 전체 μ½”λ“œλŠ” κΉƒν—ˆλΈŒμ—μ„œ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
You can find the full code on GitHub.

https://github.com/gideonslife01/flm-nextjs

πŸ“ 전체 ν”„λ‘œμ νŠΈ ꡬ쑰 / Overall Project Structure

myapp project/  
β”œβ”€β”€ app/  (Next.js App Router)
β”‚   β”œβ”€β”€ .well-known/webfinger/route.ts  -> webfinger
β”‚   β”œβ”€β”€ api/follow/route.ts    -> Follow API(temporary)
β”‚   β”œβ”€β”€ api/announce/route.ts  -> Boost(Announcement)
β”‚   β”œβ”€β”€ api/like/route.ts.     -> Like API
β”‚   β”œβ”€β”€ api/posts/route.ts     -> Writing API
β”‚   β”œβ”€β”€ api/timeline/route.ts  -> Timeline API
β”‚   β”œβ”€β”€ users/[username]/
β”‚   β”‚   β”œβ”€β”€ statuses/[id]/route.ts -> Indivisual Post
β”‚   β”‚   β”œβ”€β”€ route.ts               -> Acotr Information
β”‚   β”‚   β”œβ”€β”€ followers/route.ts     -> Followers List
β”‚   β”‚   β”œβ”€β”€ following/route.ts     -> Following List
β”‚   β”‚   β”œβ”€β”€ inbox/route.ts         -> Inbox
β”‚   β”‚   └── outbox/route.ts        -> outbox
β”‚   β”œβ”€β”€ usersui/[username]/
β”‚   β”‚   β”œβ”€β”€ page.tsx               -> Timeline UI
β”‚   β”‚   └── _components/themes/
β”‚   β”‚       β”œβ”€β”€ themeex/ThemeexTheme.tsx   -> Example Theme
β”‚   β”‚       β”œβ”€β”€ pinafore/PinaforeTheme.tsx -> Theme 1
β”‚   β”‚       β”œβ”€β”€ mastodon/MastodonTheme.tsx -> Theme 2
β”‚   β”‚       └── minimal/MinimaltTheme.tsx  -> Theme 3
β”‚   β”œβ”€β”€ layout.tsx, page.tsx, globals.css
β”‚   └── favicon.ico
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ theme.tsx              -> Theme Provider
β”‚   β”œβ”€β”€ watchThemes.ts         -> Check real-time theme changes
β”‚   β”œβ”€β”€ auth.ts                -> Authentication, User Management
β”‚   β”œβ”€β”€ ap.ts                  -> Follow,Undo,Create,Likes,Announce
β”‚   └── db.ts                  -> DB connection
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ keys/userIDs/          -> private.pem, public.pem(New)
β”‚   └── keys/                  -> private.pem, public.pem(legacy)
β”œβ”€β”€ data.sqlite                -> Database(1/3)
β”œβ”€β”€ data.sqlite-wal            -> Database(2/3)
β”œβ”€β”€ data.sqlite-shm            -> Database(3/3)
β”œβ”€β”€ Caddyfile                  -> https 
β”œβ”€β”€ instrumentation.ts         -> Background Server
└── package.json

πŸ“ ν”„λ‘œμ νŠΈ μ‹œμž‘ / Porject Start

πŸ“ λͺ¨λ“ˆ μ„€μΉ˜ / Module install

— λΉ„λ°€λ²ˆν˜Έ μ•”ν˜Έν™” / Password encryption

npm install bcrypt jsonwebtoken
npm install -D @types/bcrypt @types/jsonwebtoken

πŸ“ DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ / DB Migration

βœ”οΈ κΈ°μ‘΄ Usersν…Œμ΄λΈ”μ—μ„œ User1을 μœ μ§€ν•˜κ³  정보λ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
Retain User1 in the existing Users table and update its information.

βœ”οΈ Users ν…Œμ΄λΈ” / User Table

— email,password_hash,email_verified,vaerification_token ν•„λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
Add the email, password_hash, email_verified, and verification_token fields.

CREATE TABLE users (
  username TEXT PRIMARY KEY,
  display_name TEXT DEFAULT '',
  summary TEXT DEFAULT '',
  private_key TEXT,
  public_key TEXT,
  email TEXT UNIQUE,
  password_hash TEXT,
  email_verified INTEGER DEFAULT 0,
  verification_token TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

βœ”οΈ SQLITE

— ALTERλͺ…λ Ήμ–΄λ‘œλŠ” UNIQUEλ₯Ό μΆ”κ°€ν•  수 μ—†μ–΄μ„œ ν…Œμ΄λΈ”μ„ μƒˆλ‘œλ§Œλ“­λ‹ˆλ‹€.
Since a UNIQUE constraint cannot be added using the ALTER command, I am creating a new table.

# - ν…Œμ΄λΈ” μ‹ κ·œ 생성 / Create New Table -
 
sqlite3 data.sqlite

# - κΈ°μ‘΄ ν…Œμ΄λΈ” μ‚­μ œ(UNIQUEλŠ” ALTER둜 μž…λ ₯ λΆˆκ°€λŠ₯) -
# Delete existing table (UNIQUE constraint cannot be added via ALTER)
DROP TABLE IF EXISTS users;

# - ν…Œμ΄λΈ” 생성 / Create Table --

CREATE TABLE users (
  username TEXT PRIMARY KEY,
  display_name TEXT DEFAULT '',
  summary TEXT DEFAULT '',
  private_key TEXT,
  public_key TEXT,
  email TEXT UNIQUE,
  password_hash TEXT,
  email_verified INTEGER DEFAULT 0,
  verification_token TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

# - μ‚¬μš©μž μž…λ ₯(νŒ¨μŠ€μ›Œλ“œ 1111) --
# User input (password 1111

INSERT INTO users (username, display_name, summary, email, password_hash, email_verified)
VALUES ('user1','user1','My Fediverse account on aloy-horizon','freelifemakers@gmail.com','$2b$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW',1);

SELECT * FROM users;

— Private ,Public ν‚€ 생성 및 DBμ—…λ°μ΄νŠΈ(λ˜λŠ” κΈ°μ‘΄ ν‚€ ν™œμš©)
Generate private and public keys and update the database (or utilize existing keys)

# ν‚€ 생성 / Key Generation
cd ~/myapp31
node -e "
const crypto = require('crypto');
const fs = require('fs');
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: {type: 'spki', format: 'pem'},
  privateKeyEncoding: {type: 'pkcs8', format: 'pem'}
});
fs.writeFileSync('pri.pem', privateKey);
fs.writeFileSync('pub.pem', publicKey);
console.log('생성됨!');
console.log('private length:', privateKey.length);
console.log('public length:', publicKey.length);
"

# cat λͺ…λ Ήμ–΄λ‘œ ν‚€ 확인후 DB에 μ—…λ°μ΄νŠΈ ν•˜κΈ°
Check the key using the `cat` command and update the database.

# DB Update / DB Update
UPDATE users SET 
  private_key = '-----BEGIN PRIVATE KEY-----',
  public_key = '-----BEGIN PUBLIC KEY-----'
WHERE username = 'user1';

SELECT username, length(private_key) as pri_len, length(public_key) as pub_len FROM users;

— μ‚¬μš©μž 디렉토리 λ§Œλ“€κ³  ν‚€ 파일 볡사(db.tsλ‘œμ§μ΄λž‘ λ§žμΆ”κΈ° μœ„ν•΄μ„œ)
Create the user directory and copy the key file (to align with the db.ts logic).

# κΈ°μ‘΄νŒŒμΌμœ μ§€
# Keep existing files
# /myapp31/data/keys/

user1 % ls
private.pem     public.pem

# μ‹ κ·œνŒŒμΌ
# New File
#/myapp31/data/keys/user1

user1 % ls
private.pem     public.pem

1) νšŒμ›κ°€μž…μ‹œ ν‚€λŠ” data/keys/userid/private_key.pem or public_key.pem 으둜 μ €μž₯ 됨.

πŸ“ μ½”λ“œμΆ”κ°€ / Add code

βœ”οΈ νšŒμ›κ°€μž…μ²˜λ¦¬λ‚˜ λ‘œκ·ΈμΈκ΄€λ ¨ ν•¨μˆ˜μž…λ‹ˆλ‹€.
These are functions related to user registration and login processing.

— lib/auth.ts

... ...
export async function hashPassword(password: string) {
  ... ...
}

export async function verifyPassword(password: string, hash: string) {
  ... ...
}

export function createToken(username: string) {
  ... ...
}

export function verifyToken(token: string) {
... ...
}

export function getUser(username: string) {
  ... ...
}

export function getUserByEmail(email: string) {
 ... ...
}

//  βœ… myapp31 - μ‹ κ·œ μœ μ € 생성 (킀도 같이 생성!) / Create a new user (with keys!)
export function createUser(username: string, email: string, passwordHash: string, displayName: string = '') {
... ...

}

//  βœ… myapp31 -κΈ°μ‘΄ DB -> 파일둜 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜! / Migrate existing DB keys to files!
export function migrateKeysToFiles() {
  ... ...
}
... ...

πŸ“ μ½”λ“œ μˆ˜μ • / Code Modification

βœ”οΈ lib/db.ts

// lib/db.ts
// keys 폴더 생성! / Create keys folder!
const keysDir = path.join(process.cwd(), 'data/keys');
if (!fs.existsSync(keysDir)) {
  fs.mkdirSync(keysDir, { recursive: true });
  console.log('βœ… keys 폴더 생성/ keys folder created:', keysDir);
}

... ...

db.exec(`
CREATE TABLE IF NOT EXISTS users (
  username TEXT PRIMARY KEY,
  display_name TEXT DEFAULT '',
  summary TEXT DEFAULT '',
  private_key TEXT,
  public_key TEXT,
  email TEXT UNIQUE,
  password_hash TEXT,
  email_verified INTEGER DEFAULT 0,
  verification_token TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
`);

βœ”οΈ publick key κ²€μ¦λ‘œμ§ λΆ€λΆ„ μˆ˜μ •
Modified the public key verification logic.

— lib/ap.ts

1) Publick Key κ²€μ¦λ‘œμ§ μΆ”κ°€
Added public key verification logic.

2)getPublicKey, verifyHttpSignature, fetchActorPublicKey ν•¨μˆ˜ μΆ”κ°€
Added getPublicKey, verifyHttpSignature, and fetchActorPublicKey functions.


function getPublicKey(username: string): string {
  ... ...
}

... ... 

export async function verifyHttpSignature(
  req: Request,
  publicKeyPem: string
): Promise<boolean> { ... ...}

export async function fetchActorPublicKey(actorUrl: string): Promise<string> { ... ...
}

βœ”οΈ λ‚΄ μ„œλ²„λ‘œ νŒ”λ‘œμš° μš”μ²­μ‹œ Public Key둜 검증
Verify using the public key when a follow request is sent to my server.

— app/api/inbox/route.ts

export async function POST(req: Request, { params }: { params: Promise<{ username: string }> }) {

  const { username } = await params;
  let body: any;
  let rawBody = '';  

  try {
    // rawBodyλ₯Ό λ¨Όμ € 읽기 / Read rawBody first
     rawBody = await req.text(); 
    try {
      body = JSON.parse(rawBody);
    } catch {
      console.log('⚠️ JSON νŒŒμ‹± μ‹€νŒ¨! 빈 λ°”λ””? / Failed to parse JSON! Empty body?', rawBody.slice(0,200));
      return new Response('', { status: 202 });
    }

   // const body = await req.json();
    console.log(`πŸ“© [${username}] INBOX:`, body.type, body.actor, body.id);

   ... ...

   } 
... ...
}

1) νŒ”λ‘œμš°,μ–ΈνŒ”λ‘œμš°,κΈ€ λ°›κΈ°,μ’‹μ•„μš”,λΆ€μŠ€νŠΈ λͺ¨λ‘ publick key둜 검증
Follow, undo, receive posts, like, and boost(announce) actions are all verified using public keys.

2)DELETE μš”μ²­μ—λŠ” public key 검증 μ•ˆν•¨.
Public key verification is not performed for DELETE requests.

— DELETEμš”μ²­μ€ 본인의 계정이 μ‚­μ œλœκ±Έ λ‹€λ₯Έμ„œλ²„에 μ•Œλ¦¬λŠ” κΈ°λŠ₯μž„.
The DELETE request serves to notify other servers that your account has been deleted.

— 계정이 μ‚­μ œλμœΌλ―€λ‘œ public keyκ°€ 없을 수 있음.
The public key may be missing because the account has been deleted.

πŸ“ ν…ŒμŠ€νŠΈ / Test

βœ”οΈ λ§ˆμŠ€ν† λˆ μ„œλ²„μ—μ„œ aloy-horizon-duckdns.orgλ₯Ό μ–ΈνŒ”λ‘œμš°ν•˜κ³  λ‹€μ‹œ νŒ”λ‘œμš° ν•˜κΈ°

Mastodon

βœ”οΈ ν‚€ 검증 둜그 ν™•μΈν•˜κΈ° / Checking Key Verification Logs

Server Log

μš”ν•œλ³΅μŒ 8μž₯ 32절 / John 8:32

“그리고 λ„ˆν¬λŠ” 진리λ₯Ό μ•Œκ²Œ 될 것이며, 진리가 λ„ˆν¬λ₯Ό 자유둭게 ν•  것이닀.”

“Then you will know the truth ,and the truth will set you free”

Leave a Reply