Ghost

struxadotcloud/struxa

public
Fork0
CodeIssues0Pull requests0
1branch
struxaappswebsrcappapiservers[id]files[...path]route.ts
feat: payment providers, file manager archives, auth and onboarding polishebb2266a month ago165 lines · 6.2 KB
1import { and, eq } from "drizzle-orm";
2import type { NextRequest
3import { getAuth }
4import { db }
5import { nodes, servers
6import { safeDecrypt }
7import { recordActivity }
8
9type Params
10
11async function proxy(
12 const auth =
13 const session =
14 if (!session)
15
16 const { id,
17
18 const server =
19 where: eq(servers
20 with: { node: true
21 });
22 if (!server)
23
24 const isOwner =
25 const isAdmin =
26
27 if (!isOwner &&
28 const sub =
29 where: and(eq
30 });
31 if (!sub)
32 let permissions:
33 try {
34 permissions = JSON.
35 } catch {
36 permissions = [];
37 }
38 const hasFileAccess =
39 (p) =>
40 );
41 if (!hasFileAccess)
42 }
43
44 const node =
45 const action =
46 const daemonUrl =
47
48 const headers:
49 Authorization: `Bearer ${safeDecrypt
50 };
51 const ct =
52 if (ct) headers
53
54 let bodyJson:
55 if ((req.method
56 const contentLength =
57 if (contentLength >
58 bodyJson = (await
59 }
60
61 let archiveJson:
62 if (req.method
63 const contentLength =
64 if (contentLength >
65 archiveJson = (await
66 }
67
68 const upstream =
69 method: req.method
70 headers,
71 ...(req.body
72 });
73
74 const resHeaders:
75 const resContentType =
76 if (resContentType) resHeaders
77 const resContentDisposition =
78 if (resContentDisposition) resHeaders
79
80 if (upstream.ok
81 const filePath =
82 const ip =
83 req.headers.get
84 req.headers.get
85 null;
86
87 if (req.method
88 recordActivity({
89 eventType: "server:files.write",
90 userId: session.user
91 serverId: server.id
92 ip,
93 properties: filePath ?
94 });
95 } else if (req
96 recordActivity({
97 eventType: "server:files.read",
98 userId: session.user
99 serverId: server.id
100 ip,
101 properties: filePath ?
102 });
103 } else if (req
104 const files =
105 .slice(0, 50
106 .filter((f)
107 if (files.length
108 recordActivity({
109 eventType: "server:files.delete",
110 userId: session.user
111 serverId: server.id
112 ip,
113 properties: { file: files
114 });
115 }
116 } else if (req
117 const renames =
118 .slice(0, 50
119 .filter(
120 (f): f
121 typeof f === "
122 typeof (f as
123 typeof (f as
124 ) as { from
125 if (renames.length
126 recordActivity({
127 eventType: "server:files.rename",
128 userId: session.user
129 serverId: server.id
130 ip,
131 properties: { file: renames
132 });
133 }
134 } else if (req
135 const files =
136 .slice(0, 50
137 .filter((f)
138 if (files.length
139 recordActivity({
140 eventType: "server:files.archive",
141 userId: session.user
142 serverId: server.id
143 ip,
144 properties: { file: files
145 });
146 }
147 } else if (req
148 if (typeof archiveJson.
149 recordActivity({
150 eventType: "server:files.unarchive",
151 userId: session.user
152 serverId: server.id
153 ip,
154 properties: { file: archiveJson
155 });
156 }
157 }
158 }
159
160 return new Response
161}
162
163export const GET =
164export const POST =
165export const PUT =
}
from
"
next/server
"
;
from
"
@struxa/auth
"
;
from
"
@struxa/db
"
;
,
subusers
}
from
"
@struxa/db
"
;
from
"
@struxa/api/lib/crypto
"
;
from
"
@struxa/api/services/activity
"
;
=
{
params
:
Promise
<
{
id
:
string
;
path
:
string
[]
}
>
}
;
req
:
NextRequest
,
{
params
}
:
Params
)
{
await
getAuth
()
;
await
auth
.
api
.
getSession
(
{
headers
:
req
.
headers
}
)
;
return
new
Response
(
"
Unauthorized
"
,
{
status
:
401
}
)
;
path
}
=
await
params
;
await
db
.
query
.
servers
.
findFirst
(
{
.
uuid
,
id
)
,
}
,
return
new
Response
(
"
Not Found
"
,
{
status
:
404
}
)
;
server
.
userId
===
session
.
user
.
id
;
session
.
user
.
role
===
"
admin
"
;
!
isAdmin
)
{
await
db
.
query
.
subusers
.
findFirst
(
{
(
subusers
.
userId
,
session
.
user
.
id
)
,
eq
(
subusers
.
serverId
,
server
.
id
))
,
return
new
Response
(
"
Forbidden
"
,
{
status
:
403
}
)
;
string
[]
=
[]
;
parse
(
sub
.
permissions
)
as
string
[]
;
permissions
.
some
(
p
===
"
*
"
||
p
===
"
files
"
||
p
===
"
file
"
||
p
.
startsWith
(
"
file.
"
)
,
return
new
Response
(
"
Forbidden
"
,
{
status
:
403
}
)
;
server
.
node
as
typeof
nodes
.
$inferSelect
;
path
[
path
.
length
-
1
]
;
`
${
node
.
scheme
}
://
${
node
.
fqdn
}
:
${
node
.
daemonListen
}
/api/servers/
${
server
.
uuid
}
/files/
${
path
.
join
(
"
/
"
)
}${
req
.
nextUrl
.
search
}
`
;
Record
<
string
,
string
>
=
{
(
node
.
token
)
}
`
,
req
.
headers
.
get
(
"
Content-Type
"
)
;
[
"
Content-Type
"
]
=
ct
;
{
files
?
:
unknown
[]
}
|
null
=
null
;
===
"
POST
"
&&
action
===
"
delete
"
)
||
(
req
.
method
===
"
PUT
"
&&
action
===
"
rename
"
))
{
Number
(
req
.
headers
.
get
(
"
content-length
"
)
??
0
)
;
64
*
1024
)
return
new
Response
(
"
Payload Too Large
"
,
{
status
:
413
}
)
;
req
.
clone
()
.
json
()
.
catch
(
()
=>
null
))
as
{
files
?
:
unknown
[]
}
|
null
;
{
files
?
:
unknown
[]
;
file
?
:
unknown
}
|
null
=
null
;
===
"
POST
"
&&
(
action
===
"
compress
"
||
action
===
"
decompress
"
))
{
Number
(
req
.
headers
.
get
(
"
content-length
"
)
??
0
)
;
64
*
1024
)
return
new
Response
(
"
Payload Too Large
"
,
{
status
:
413
}
)
;
req
.
clone
()
.
json
()
.
catch
(
()
=>
null
))
as
{
files
?
:
unknown
[]
;
file
?
:
unknown
}
|
null
;
await
fetch
(
daemonUrl
,
{
,
?
{
body
:
req
.
body
,
duplex
:
"
half
"
}
:
{}
)
,
Record
<
string
,
string
>
=
{}
;
upstream
.
headers
.
get
(
"
Content-Type
"
)
;
[
"
Content-Type
"
]
=
resContentType
;
upstream
.
headers
.
get
(
"
Content-Disposition
"
)
;
[
"
Content-Disposition
"
]
=
resContentDisposition
;
)
{
req
.
nextUrl
.
searchParams
.
get
(
"
file
"
)
;
(
"
x-forwarded-for
"
)
?.
split
(
"
,
"
)[
0
]
?.
trim
()
??
(
"
x-real-ip
"
)
??
===
"
POST
"
&&
action
===
"
write
"
)
{
.
id
,
,
{
file
:
filePath
}
:
undefined
,
.
method
===
"
GET
"
&&
action
===
"
contents
"
)
{
.
id
,
,
{
file
:
filePath
}
:
undefined
,
.
method
===
"
POST
"
&&
action
===
"
delete
"
&&
bodyJson
)
{
(
bodyJson
.
files
??
[])
)
:
f
is
string
=>
typeof
f
===
"
string
"
)
;
>
0
)
{
.
id
,
,
.
join
(
"
,
"
)
.
slice
(
0
,
2000
)
}
,
.
method
===
"
PUT
"
&&
action
===
"
rename
"
&&
bodyJson
)
{
(
bodyJson
.
files
??
[])
)
is
{
from
:
string
;
to
:
string
}
=>
object
"
&&
f
!==
null
&&
{
from
?
:
unknown
}
)
.
from
===
"
string
"
&&
{
to
?
:
unknown
}
)
.
to
===
"
string
"
,
:
string
;
to
:
string
}
[]
;
>
0
)
{
.
id
,
,
.
map
(
(
f
)
=>
`
${
f
.
from
}
→
${
f
.
to
}
`
)
.
join
(
"
,
"
)
.
slice
(
0
,
2000
)
}
,
.
method
===
"
POST
"
&&
action
===
"
compress
"
&&
archiveJson
)
{
(
archiveJson
.
files
??
[])
)
:
f
is
string
=>
typeof
f
===
"
string
"
)
;
>
0
)
{
.
id
,
,
.
join
(
"
,
"
)
.
slice
(
0
,
2000
)
}
,
.
method
===
"
POST
"
&&
action
===
"
decompress
"
&&
archiveJson
)
{
file
===
"
string
"
)
{
.
id
,
,
.
file
.
slice
(
0
,
2000
)
}
,
(
upstream
.
body
,
{
status
:
upstream
.
status
,
headers
:
resHeaders
}
)
;
proxy
;
proxy
;
proxy
;