A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 192 lines 5.1 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package proxy 18 19import ( 20 "fmt" 21 "net" 22 "net/http" 23 "net/http/httputil" 24 25 "github.com/siyuan-note/logging" 26 "github.com/siyuan-note/siyuan/kernel/model" 27 "github.com/siyuan-note/siyuan/kernel/util" 28) 29 30type PublishServiceTransport struct{} 31 32var ( 33 Host = "0.0.0.0" 34 Port = "0" 35 36 listener net.Listener 37 transport = PublishServiceTransport{} 38 proxy = &httputil.ReverseProxy{ 39 Rewrite: rewrite, 40 Transport: transport, 41 } 42) 43 44func InitPublishService() (uint16, error) { 45 model.InitAccounts() 46 47 if listener != nil { 48 if !model.Conf.Publish.Enable { 49 // 关闭发布服务 50 closePublishListener() 51 return 0, nil 52 } 53 54 if port, err := util.ParsePort(Port); err != nil { 55 return 0, err 56 } else if port != model.Conf.Publish.Port { 57 // 关闭原端口的发布服务 58 if err = closePublishListener(); err != nil { 59 return 0, err 60 } 61 62 // 重新启动新端口的发布服务 63 initPublishService() 64 } 65 } else { 66 if !model.Conf.Publish.Enable { 67 return 0, nil 68 } 69 70 // 启动新端口的发布服务 71 initPublishService() 72 } 73 return util.ParsePort(Port) 74} 75 76func initPublishService() (err error) { 77 if err = initPublishListener(); err == nil { 78 go startPublishReverseProxyService() 79 } 80 return 81} 82 83func initPublishListener() (err error) { 84 // Start new listener 85 listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", Host, model.Conf.Publish.Port)) 86 if err != nil { 87 logging.LogErrorf("start listener failed: %s", err) 88 return 89 } 90 91 _, Port, err = net.SplitHostPort(listener.Addr().String()) 92 if err != nil { 93 logging.LogErrorf("split host and port failed: %s", err) 94 return 95 } 96 return 97} 98 99func closePublishListener() (err error) { 100 listener_ := listener 101 listener = nil 102 if err = listener_.Close(); err != nil { 103 logging.LogErrorf("close listener %s failed: %s", listener_.Addr().String(), err) 104 listener = listener_ 105 } 106 return 107} 108 109func startPublishReverseProxyService() { 110 logging.LogInfof("publish service [%s:%s] is running", Host, Port) 111 // 服务进行时一直阻塞 112 if err := http.Serve(listener, proxy); err != nil { 113 if listener != nil { 114 logging.LogErrorf("boot publish service failed: %s", err) 115 } 116 } 117 logging.LogInfof("publish service [%s:%s] is stopped", Host, Port) 118} 119 120func rewrite(r *httputil.ProxyRequest) { 121 r.SetURL(util.ServerURL) 122 r.SetXForwarded() 123 // r.Out.Host = r.In.Host // if desired 124} 125 126func (PublishServiceTransport) RoundTrip(request *http.Request) (response *http.Response, err error) { 127 if model.Conf.Publish.Auth.Enable { 128 // Session Auth 129 sessionIdCookie, cookieErr := request.Cookie(model.SessionIdCookieName) 130 if cookieErr == nil { 131 // Check session ID 132 sessionID := sessionIdCookie.Value 133 if username := model.GetBasicAuthUsernameBySessionID(sessionID); username != "" { 134 // Valid session 135 if account := model.GetBasicAuthAccount(username); account != nil { 136 // Valid account 137 request.Header.Set(model.XAuthTokenKey, account.Token) 138 response, err = http.DefaultTransport.RoundTrip(request) 139 return 140 } else { 141 // Invalid account, remove session 142 model.DeleteSession(sessionID) 143 } 144 } 145 } 146 147 // Basic Auth 148 username, password, ok := request.BasicAuth() 149 account := model.GetBasicAuthAccount(username) 150 if !ok || 151 account == nil || 152 account.Username == "" || // 匿名用户 153 account.Password != password { 154 155 return &http.Response{ 156 StatusCode: http.StatusUnauthorized, 157 Status: http.StatusText(http.StatusUnauthorized), 158 Proto: request.Proto, 159 ProtoMajor: request.ProtoMajor, 160 ProtoMinor: request.ProtoMinor, 161 Request: request, 162 Header: http.Header{ 163 model.BasicAuthHeaderKey: {model.BasicAuthHeaderValue}, 164 }, 165 Body: http.NoBody, 166 Close: false, 167 ContentLength: -1, 168 }, nil 169 } else { 170 // set session cookie 171 sessionID := model.GetNewSessionID() 172 cookie := &http.Cookie{ 173 Name: model.SessionIdCookieName, 174 Value: sessionID, 175 Path: "/", 176 HttpOnly: true, 177 } 178 model.AddSession(sessionID, username) 179 180 // set JWT 181 request.Header.Set(model.XAuthTokenKey, account.Token) 182 response, err = http.DefaultTransport.RoundTrip(request) 183 184 response.Header.Add("Set-Cookie", cookie.String()) 185 return 186 } 187 } else { 188 request.Header.Set(model.XAuthTokenKey, model.GetBasicAuthAccount("").Token) 189 response, err = http.DefaultTransport.RoundTrip(request) 190 return 191 } 192}