ππ» 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

βοΈ λ‘κ·Έμμμ μ’μμ,λΆμ€νΈ ν΄λ¦νλ©΄ λ‘κ·ΈμΈ νμ΄μ§λ‘ μ΄λ
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

— GTS Server

μν볡μ 8μ₯ 32μ / John 8:32
“κ·Έλ¦¬κ³ λν¬λ μ§λ¦¬λ₯Ό μκ² λ κ²μ΄λ©°, μ§λ¦¬κ° λν¬λ₯Ό μμ λ‘κ² ν κ²μ΄λ€.”
“Then you will know the truth ,and the truth will set you free”