[nextjs]SNS Server-43(myapp53) 

👉🏻 myapp53에서는 부스트와 좋아요 부분 보안을 강화합니다.
myapp53 is strengthening security for the Boost and Like features.

👉🏻 로그인 하지 않으면 부스트나 좋아요를 클릭하면 로그인화면으로 이동합니다.
If you are not logged in, clicking “Boost” or “Like” will redirect you to the login screen.

👉🏻 사용하지않는 getToken함수를 삭제합니다.
Remove the unused getToken function.

👉🏻 로컬 서버에서 글 작성할 때 첨부 파일이 있는 경우 첨부파일 리스트가 초기화 되지 않는 문제를 수정합니다.
Fixes an issue where the attachment list was not reset when creating a post with attachments on a local server.

👉🏻 전체 코드는 깃허브에서 확인 할 수 있습니다.
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(deprecated)
│   ├── api/timeline/route.ts  -> Timeline API
│   ├── api/followinglist/route.ts  -> followinglist API
│   ├── api/followerslist/route.ts  -> followerslist API
│   ├── api/v1/instance/route.ts -> auth
│   ├── api/v1/apps/route.ts.    -> auth
│   ├── api/v1/accounts/verify_credentials/route.ts -> auth
│   ├── api/v1/statuses/route.ts -> Write Post
│   ├── api/v1/timelines/home/route.ts -> timeline
│   ├── api/v1/media/route.ts -> Image upload
│   ├── api/v1/media/[id]/route.ts  -> Image Delete 
│   ├── oauth/authorize/route.ts -> auth
│   ├── oauth/token/route.ts -> auth
│   ├── api/v1/search/home/route.ts -> search
│   ├── api/v2/search/home/route.ts -> search
│   ├── api/v1/accounts/[id]/followers/route.ts -> followers
│   ├── api/v1/accounts/[id]/following/route.ts -> following
│   ├── api/v1/statuses/[id]/route.ts -> -> Post delete(pinafore)
│   ├── api/auth/signup/route.ts        -> Signup
│   ├── api/auth/login/route.ts         -> Login
│   ├── api/auth/logout/route.ts        -> Logout
│   ├── api/auth/refresh/route.ts       -> Refresh Token
│   ├── api/cleanup-orphan/route.ts     -> post,outbox clean up
│   ├── api/auth/me/route.tsx           -> Login Check
│   ├── auth/signup/page.tsx            -> Signup UI
│   ├── auth/signup/page.tsx            -> Login UI
│   ├── 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(Server, 404)
│   │   ├── ClientPage.tsx.        -> Timeline UI(Client,Call Active Themes)
│   │   └── _components/themes/
│   │       ├── themeex/ThemeexTheme.tsx   -> Example Theme
│   │       ├── pinafore/PinaforeTheme.tsx -> Theme 1
│   │       ├── pinafore/FollowersList.tsx -> Theme 1, FollowersList
│   │       ├── pinafore/FollowingList.tsx -> Theme 1, FollowingList
│   │       ├── pinafore/NotificationsList.tsx -> Theme 1, NotificationsList
│   │       ├── 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
│   ├── visibility.ts          -> Visibility(Public,Unlisted,Private,Direct)
│   └── 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

📁 프로젝트 시작 / Project Start


📁 코드수정 / Code Modification

✔️ 버그 수정 / Bug fixes

— 글 작성 후 이미지 첨부 리스트와 프리뷰를 초기화 합니다.
After posting, the list of attached images and the preview are reset.

A.app/usersui/[username]/_components/themes/pinafore/PinaforeTheme.tsx

const handlePost = async () => {
  if (!composeText.trim() || posting) return;
  setPosting(true);
  try {
    // 토큰 있으면 헤더에 넣고 없으면 쿠키 인증으로
    // Include the token in the header if present; otherwise, use cookie authentication.
    const token = localStorage.getItem('access_token') || localStorage.getItem('token') || '';
    const res = await fetch('/api/v1/statuses', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
         ...(token? { Authorization: `Bearer ${token}` } : {})
      },
      credentials: 'include',
      body: JSON.stringify({ 
        status: composeText, 
        visibility: composeVis, 
        media_ids: mediaIds // ✅ myapp51 - add media_ids
      })
    });
    if (res.ok) {
      const newPost = await res.json();
      setComposeText('');
      setMediaIds([]); // ✅ myapp53 - 첨부 리스트 초기화 / Reset attachment list
      setPreviews([]); // ✅ myapp53 - 프리뷰 초기화 / Reset Preview
      // 타임라인 맨 위에 즉시 추가 / Add immediately to the top of the timeline
      setTimeline((prev: any[]) => [{
        id: newPost.id, content: newPost.content, actor: `https://${DOMAIN}/users/${username}`,
        username: username, 
        created_at: new Date().toISOString(), 
        source: 'local', 
        isMine: true, 
        visibility: composeVis,
        media_attachments: newPost.media_attachments || [] // ✅ myap51 - preview
      },...prev]);
      setCounts(c => ({...c, posts: c.posts + 1}));
    } else alert('게시 실패');
    } catch (e) { console.error(e); alert('게시 실패'); }
  setPosting(false);
  
};

✔️ 부스트 보안강화 / Boost Security Enhancement

— 로그인하지 않으면 부스트 기능을 사용할 수 없도록 합니다.
Disable the use of the boost function if the user is not logged in.

A.app/api/announce/route.ts – 백엔드 / Backend

... ...
function getUser(req: Request) {
  const auth = req.headers.get('Authorization')?.replace('Bearer ','').trim();
  if (auth) {
    const o = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(auth) as any;
    if (o?.username) return o.username;
  }
  const cookie = req.headers.get('cookie') || '';
  const m = cookie.match(/refresh_token=([^;]+)/);
  if (m) {
    const s = db.prepare('SELECT username FROM sessions WHERE refresh_token=?').get(m[1]) as any;
    if (s?.username) return s.username;
  }
  return null;
}

export async function POST(req: NextRequest) {
  try {
    //const { username, target } = await req.json();
    const authUser = getUser(req);
    if (!authUser) {
      return NextResponse.json({ error: '로그인 필요 / authentication required' }, { status: 401 });
    }
    const { target } = await req.json(); 
    const username = authUser; 
... ...
}

export async function DELETE(req: NextRequest) {
  try {
    // ✅ myapp53 - 부스트 삭제 보안강화 / Boost Removal & Security Enhancement
    //const { username, target } = await req.json();
    const authUser = getUser(req);
    if (!authUser) {
      return NextResponse.json({ error: '로그인 필요/ authentication required' }, { status: 401 });
    }
    const { target } = await req.json();
    const username = authUser;
... ...
}

B.app/usersui/[username]/_components/themes/pinafore/PinaforeTheme.tsx – 프론트엔드 / Frontend

a.부스트 보안강화 / Boost Security Enhancement

  ... ...
  // ✅ myapp53-boost-secure: 부스트 보안강화 Boost Security Enhancement
  const handleBoostClick = async (p: any) => {
    if (!currentUser) return window.location.href = `/auth/login?next=/@${username}`;
    onBoost(p)
  };

  // ✅ myapp53-boost-secure: 좋아요 보안강화 / Like Security Enhancement
  const handleLikeClick = (p: any) => {
    if (!currentUser) return window.location.href = `/auth/login?next=/@${username}`;
    onLike(p);
  };
... ...

b.버그수정(사용하지 않는 getToken함수 부분) / Bug fix (unused getToken function)

... ...
// ✅ deprecated(myapp53)-myapp51 - 토큰가져오가 , url 경로보정 / Retrieve token, adjust URL path
// ✅ myapp53 - 토큰 제거, 쿠키 인증만 사용 / cookie auth only
//const getToken = () => localStorage.getItem('access_token') || localStorage.getItem('token') || '';
... ...
 // ✅ myapp53 - cookie auth only
 //const token = localStorage.getItem('access_token') || localStorage.getItem('token') || '';
const handleDelete = async (postId: string) => {
... ...
    const res = await fetch(`/api/v1/statuses/${postId}`, { 
      method: 'DELETE',
      // headers: {
      //   ...(token? { Authorization: `Bearer ${token}` } : {})
      // },
      credentials: 'include' // refresh_token 쿠키 포함 / Include refresh_token cookie
    });
... ...
}
... ...
const handlePost = async () => {
... ...
    // ✅ myapp53 - cookie auth only
    const res = await fetch('/api/v1/statuses', {
      method: 'POST',
      // headers: {
      //   'Content-Type': 'application/json',
      //    ...(token? { Authorization: `Bearer ${token}` } : {})
      // },
      credentials: 'include',
      body: JSON.stringify({ 
        status: composeText, 
        visibility: composeVis, 
        media_ids: mediaIds // ✅ myapp51 - add media_ids
      })
    });
... ...
    if (res.ok) {
      const newPost = await res.json();
      setComposeText('');
      setMediaIds([]); // ✅ myapp53 - 첨부 리스트 초기화 / Reset attachment list
      setPreviews([]); // ✅ myapp53 - 프리뷰 초기화 / Reset Preview
... ...
    }
... ...
}

async function onFiles(e: React.ChangeEvent<HTMLInputElement>) {
    // ✅ myapp53 - Cookie auth only
    //const token = getToken();
... ...
        const res = await fetch('/api/v1/media', {
          method: 'POST',
          body: fd,
          //headers: {...(token? { Authorization: `Bearer ${token}` } : {}) },// ✅ myapp53 - Cookie auth only
          credentials: 'include'
        });
... ...
}

✔️ 좋아요 보안강화 / Like Security Enhancement

— 로그인하지 않으면 좋아요 기능을 사용할 수 없도록 합니다.
Disable the ‘like’ function for users who are not logged in.

A.app/api/announce/route.ts – 백엔드 / Backend

... ...
function getUser(req: Request) {
  const auth = req.headers.get('Authorization')?.replace('Bearer ','').trim();
  if (auth) {
    const o = db.prepare('SELECT username FROM oauth_tokens WHERE access_token=?').get(auth) as any;
    if (o?.username) return o.username;
  }
  const cookie = req.headers.get('cookie') || '';
  const m = cookie.match(/refresh_token=([^;]+)/);
  if (m) {
    const s = db.prepare('SELECT username FROM sessions WHERE refresh_token=?').get(m[1]) as any;
    if (s?.username) return s.username;
  }
  return null;
}

export async function POST(req: NextRequest) {
  //const { username, target } = await req.json();
  // ✅ myapp53 - 좋아요 보안강화 / like Security Enhancement
  //const { username, target } = await req.json();
  const authUser = getUser(req);
  if (!authUser) {
    return NextResponse.json({ error: '로그인 필요 / authentication required' }, { status: 401 });
  }
  const { target } = await req.json();
  const username = authUser;
... ...
}

export async function DELETE(req: NextRequest) {

  // ✅ myapp53 - 좋아요 보안강화 / like Security Enhancement
  //const { username, target } = await req.json();
  const authUser = getUser(req);
  if (!authUser) {
    return NextResponse.json({ error: '로그인 필요 / authentication required' }, { status: 401 });
  }
  const { target } = await req.json();
  const username = authUser;
... ...
}

B.app/usersui/[username]/_components/themes/pinafore/PinaforeTheme.tsx – 프론트엔드 / Frontend

— 위의 부스트 보안강화 부분의 코드를 참조하세요
Please refer to the code in the Boost security enhancement section above.

📁 테스트 / Test

✔️ 첨부파일 리스트 초기화 테스트
Attachment list reset test

Attachment list reset

✔️ 로그아웃시 좋아요,부스트 클릭하면 로그인 페이지로 이동
If you click “Like” or “Boost” while logged out, you will be redirected to the login page.

✔️ 로그인시 좋아요,부스트 작동확인
Verify that ‘Like’ and ‘Boost’ functions work upon login.

— Local Server

Local Server

— GTS Server

GTS Server

요한복음 8장 32절 / John 8:32

“그리고 너희는 진리를 알게 될 것이며, 진리가 너희를 자유롭게 할 것이다.”

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

Leave a Reply