Skip to content

Commit b8cae2b

Browse files
committed
chat-widget
1 parent 2921c6e commit b8cae2b

30 files changed

+17133
-40
lines changed

src/Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ gotty: asset ${TARGET}/main.go utils/*.go server/*.go webtty/*.go filebrowser/*.
2020
#godep go build ${BUILD_OPTIONS}
2121

2222
.PHONY: asset
23-
asset: bindata/static/js/gotty-bundle.js bindata/static/js/jsconsole.js bindata/static/index.html bindata/static/doc.html bindata/static/editblog.html bindata/static/about.html bindata/static/NewFile.html bindata/static/images bindata/static/css bindata/static/css/xterm.css bindata/static/meta
23+
asset: bindata/static/js/gotty-bundle.js bindata/static/js/chat-widget.js bindata/static/js/jsconsole.js bindata/static/index.html bindata/static/doc.html bindata/static/editblog.html bindata/static/about.html bindata/static/NewFile.html bindata/static/images bindata/static/css bindata/static/css/xterm.css bindata/static/meta
2424
$(ROOT)/bin/go-bindata -prefix bindata -pkg server -ignore=\\.gitkeep -o server/asset.go bindata/...
2525
GO111MODULE=off $(GOROOT)/bin/gofmt -w server/asset.go
2626

@@ -108,6 +108,16 @@ jsconsole/build/static/js/jsconsole.js: jsconsole/node_modules/webpack $(JS_FILE
108108
bindata/static/js/jsconsole.js: jsconsole/build/static/js/jsconsole.js
109109
cp jsconsole/build/static/js/jsconsole.js bindata/static/js/jsconsole.js
110110

111+
CHAT_JS_FILES := $(wildcard resources/chat-widget/src/widget.*) $(wildcard resources/chat-widget/*.json) $(wildcard resources/chat-widget/*.js) resources/chat-widget/src/index.ts
112+
113+
resources/chat-widget/dist/index.umd.js: $(CHAT_JS_FILES)
114+
@echo "building chat-widget: "
115+
cd resources/chat-widget && \
116+
npm install
117+
118+
bindata/static/js/chat-widget.js: resources/chat-widget/dist/index.umd.js
119+
cp resources/chat-widget/dist/index.umd.js bindata/static/js/chat-widget.js
120+
111121
deb: all
112122
mkdir -p ${DEBIAN_ROOT}/gotty/usr/lib/systemd/system
113123
mkdir -p ${DEBIAN_ROOT}/gotty/usr/local/bin
@@ -141,6 +151,9 @@ clean:
141151
cleanjs:
142152
rm -rf js/node_modules
143153
rm -rf jsconsole/node_modules
154+
rm -rf resources/chat-widget/node_modules
155+
rm -rf js/dist/*
156+
rm -rf resources/chat-widget/dist/*
144157
@echo ".... Clean Done"
145158

146159

src/cookie/cookie.go

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
)
1414

1515
const MAX_CONN_PER_BROWSER = 1
16+
// this key will be used for encryption and decdryption of tokens
17+
var SECRET_KEY []byte = encoder.GenerateLargePrime().Bytes()
1618

1719
// handling for cookies and incrementing session cookie counter
1820
func IncrementCounterCookies(rw http.ResponseWriter, req *http.Request) {
@@ -68,8 +70,8 @@ func GetCounterCookieValue(req *http.Request) int {
6870
var session_store *sessions.CookieStore
6971

7072
// initilize the cookiestore with secret stored in session DB
71-
func Init_SessionStore(secret string) {
72-
session_store = sessions.NewCookieStore([]byte(secret))
73+
func Init_SessionStore(secret []byte) {
74+
session_store = sessions.NewCookieStore(secret)
7375
}
7476

7577
func Get_SessionStore() *sessions.CookieStore {
@@ -82,14 +84,15 @@ func init() {
8284
panic(errors.New("uninitialized session_db_handle!!"))
8385
}
8486
secret , err := session_db_handle.Fetch([]byte(user.SESSION_KEY))
85-
log.Println("secret: ", secret, err)
87+
log.Println("secret fetched: ", err)
8688
if err != nil {
8789
// failed to fetch secret, generate a temporary secret for this instance
8890
secret = encoder.GenerateLargePrime().Bytes()
89-
log.Println("Failed to fetch secret for session_cookie, generated temporary secret: ", secret, err)
91+
log.Println("Failed to fetch secret for session_cookie, generated temporary secret. ", err)
9092
}
93+
SECRET_KEY = secret
9194
// init one time session store using SESSION_KEY
92-
Init_SessionStore(string(secret))
95+
Init_SessionStore(secret)
9396

9497
}
9598

@@ -107,14 +110,23 @@ func Set_SessionCookie(rw http.ResponseWriter, req *http.Request, session user.U
107110
session_cookie.Values ["expirationTime"] = session.ExpirationTime
108111
session_cookie.Values [utils.HOME_DIR_KEY] = user.GetHomeDir(session.Uid)
109112

113+
// number of api request remaining in seconds
114+
var req_count_rem float64 = float64(session.ExpirationTime-utils.GetUnixMilli())/1000
110115
// set maxage of the session
111116
session_cookie.Options = &sessions.Options{
112117
Path: "/",
113-
MaxAge: int(session.ExpirationTime-utils.GetUnixMilli())/1000,
118+
MaxAge: int(req_count_rem),
114119
}
115120

116-
log.Println("session cookie save: ", int(session.ExpirationTime-utils.GetUnixMilli())/1000, session_cookie)
121+
if session.LoggedIn {
122+
session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY] = (req_count_rem/60)*utils.USER_FACTOR
123+
} else {
124+
session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY] = (req_count_rem/60)*utils.GUEST_FACTOR
125+
}
126+
127+
log.Println("session cookie save: ", int(session.ExpirationTime-utils.GetUnixMilli())/1000)
117128
return session_store.Save(req, rw, session_cookie)
129+
118130
}
119131

120132

@@ -127,6 +139,8 @@ func Delete_SessionCookie(rw http.ResponseWriter, req *http.Request, session use
127139
session_cookie.Values["uid"] = session.Uid
128140
session_cookie.Values["sessionID"] = session.SessionID
129141
session_cookie.Values ["loggedIn"] = false
142+
delete(session_cookie.Values, utils.OPENAI_REQUEST_COUNT_KEY)
143+
delete(session_cookie.Values, utils.OPENAI_REQUEST_LAST_ACCESS)
130144

131145
// set maxage of the session
132146
session_cookie.Options = &sessions.Options{
@@ -187,6 +201,78 @@ func IsSessionExpired(req *http.Request) bool {
187201
return false
188202
}
189203

204+
func GetOpenApiRequestCount(req *http.Request) (count float64) {
205+
session_cookie, _ := session_store.Get(req, "user-session")
206+
val := session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY]
207+
count, ok := val.(float64);
208+
if !ok {
209+
return float64(0)
210+
}
211+
return count
212+
}
213+
214+
func SetOpenApiRequestCount(rw http.ResponseWriter, req *http.Request, count float64) (err error) {
215+
session_cookie, _ := session_store.Get(req, "user-session")
216+
session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY] = count
217+
return session_store.Save(req, rw, session_cookie)
218+
}
219+
220+
func GetOpenApiLastAccessTime(req *http.Request) (lastaccesstime int64) {
221+
session_cookie, _ := session_store.Get(req, "user-session")
222+
val := session_cookie.Values[utils.OPENAI_REQUEST_LAST_ACCESS]
223+
lastaccesstime, ok := val.(int64);
224+
if !ok {
225+
return utils.GetUnixMilli() - utils.DEADLINE_MINUTES*60*1000
226+
}
227+
return lastaccesstime
228+
}
229+
230+
/*
231+
to be called whenever openAIapi request is made, to recharge the request count
232+
based on current time lapsed, it should eb able to give n number of requests per minute,
233+
as given by user or GUEST_FACTOR
234+
*/
235+
func UpdateOpenApiRequestCountBalance(rw http.ResponseWriter, req *http.Request) (err error) {
236+
session_cookie, _ := session_store.Get(req, "user-session")
237+
// recharge req_count balance in sec
238+
current_time_mili := utils.GetUnixMilli()
239+
var req_count_balance_sec float64 = float64(current_time_mili-GetOpenApiLastAccessTime(req))/1000
240+
241+
var req_count_balance float64 = 0
242+
var max_cap float64 = utils.GUEST_FACTOR*utils.DEADLINE_MINUTES // max num of request per minute a user can make
243+
if Is_UserLoggedIn(req) {
244+
req_count_balance = GetOpenApiRequestCount(req)+(req_count_balance_sec/60)*utils.USER_FACTOR
245+
max_cap = utils.USER_FACTOR*utils.DEADLINE_MINUTES
246+
} else {
247+
req_count_balance = GetOpenApiRequestCount(req)+(req_count_balance_sec/60)*utils.GUEST_FACTOR
248+
max_cap = utils.GUEST_FACTOR*utils.DEADLINE_MINUTES
249+
}
250+
if max_cap >= req_count_balance {
251+
session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY] = req_count_balance
252+
} else {
253+
session_cookie.Values[utils.OPENAI_REQUEST_COUNT_KEY] = max_cap
254+
}
255+
// not exceeding request balance more that maxcap req per user
256+
session_cookie.Values[utils.OPENAI_REQUEST_LAST_ACCESS] = current_time_mili
257+
return session_store.Save(req, rw, session_cookie)
258+
}
259+
260+
func GetOpenApiAccessToken(req *http.Request) (acc_token, secret []byte) {
261+
session_cookie, _ := session_store.Get(req, "user-session")
262+
val := session_cookie.Values[utils.ACCESS_TOKEN_KEY]
263+
secretval := session_cookie.Values[utils.ACCESS_SECRET_KEY]
264+
acc_token, _ = val.([]byte);
265+
secret, _ = secretval.([]byte);
266+
return acc_token, secret
267+
}
268+
269+
func SetOpenApiAccessToken(rw http.ResponseWriter, req *http.Request, acc_token, secret []byte) (err error) {
270+
session_cookie, _ := session_store.Get(req, "user-session")
271+
session_cookie.Values[utils.ACCESS_TOKEN_KEY] = acc_token
272+
session_cookie.Values[utils.ACCESS_SECRET_KEY] = secret
273+
return session_store.Save(req, rw, session_cookie)
274+
}
275+
190276
func Get_SessionCookie(req *http.Request) (session user.UserSession) {
191277
session.Uid = Get_Uid(req)
192278
session.SessionID = Get_SessionID(req)
@@ -205,6 +291,7 @@ func UpdateGuestSessionCookieAge(rw http.ResponseWriter, req *http.Request, newa
205291

206292
if !Is_UserLoggedIn(req) || IsSessionExpired(req) {
207293
// only update age if user not logged in
294+
208295
maxage = newage
209296
}
210297
// update maxage (sec) of the session
@@ -213,7 +300,7 @@ func UpdateGuestSessionCookieAge(rw http.ResponseWriter, req *http.Request, newa
213300
MaxAge: maxage,
214301
}
215302
err = session_store.Save(req, rw, session_cookie)
216-
log.Println("session cookie save: ", maxage, session_cookie, "Error: ", err)
303+
log.Println("session cookie save: ", maxage, "Error: ", err)
217304
return err
218305
}
219306

@@ -244,7 +331,7 @@ func GetOrUpdateHomeDir(rw http.ResponseWriter, req *http.Request, Uid string) (
244331
var ok bool
245332

246333
homedir, ok = session_cookie.Values[utils.HOME_DIR_KEY].(string);
247-
log.Println("previous homedir: ", homedir, ok, session_cookie)
334+
log.Println("previous homedir: ", homedir, ok)
248335
// check if same uid amd valid home dir, if not generate a new homedir
249336
if Uid!=Get_Uid(req) || !ok || homedir=="" {
250337
homedir = user.GetHomeDir(Uid)

src/encoder/encode.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ package encoder
33
import (
44
"encoding/base64"
55
"log"
6-
"crypto/rand"
7-
"math/big"
6+
"crypto/aes"
7+
"crypto/cipher"
8+
"crypto/rand"
9+
"math/big"
10+
"io"
11+
"fmt"
812
)
913

10-
// use 32 bits for type int
11-
const SECRET_BITSIZE = 16
14+
// use 16 byte, default 128 bit for AES
15+
const SECRET_BITSIZE = 16*8
1216

13-
func GenerateLargePrime() *big.Int {
14-
// generate large prime
17+
func GenerateLargePrime(bits ...int) *big.Int {
18+
var bitsize int = SECRET_BITSIZE
19+
if len(bits) > 0 {
20+
bitsize = bits[0]
21+
}
1522

1623
// Generate a random prime number
17-
prime, err := rand.Prime(rand.Reader, SECRET_BITSIZE)
24+
prime, err := rand.Prime(rand.Reader, bitsize)
1825
if err != nil {
1926
log.Println("Error:", err)
2027
// any prime number, probably we won't reach here
@@ -49,4 +56,56 @@ func DecodeToPID(jid string) int {
4956
return -1
5057
}
5158

59+
// Encrypt encrypts the plaintext using AES-GCM with the given key and returns a URL-safe base64-encoded ciphertext.
60+
func Encrypt(plainTextBytes, keyBytes []byte) ([]byte, error) {
5261

62+
block, err := aes.NewCipher(keyBytes)
63+
if err != nil {
64+
return []byte(""), err
65+
}
66+
67+
aesGCM, err := cipher.NewGCM(block)
68+
if err != nil {
69+
return []byte(""), err
70+
}
71+
72+
nonce := make([]byte, aesGCM.NonceSize())
73+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
74+
return []byte(""), err
75+
}
76+
77+
cipherText := aesGCM.Seal(nonce, nonce, plainTextBytes, nil)
78+
urlSafeCipherText := RawURLEncoding.EncodeToString(cipherText)
79+
return []byte(urlSafeCipherText), nil
80+
}
81+
82+
// Decrypt decrypts the URL-safe base64-encoded ciphertext using AES-GCM with the given key.
83+
func Decrypt(cipherText, keyBytes []byte) ([]byte, error) {
84+
cipherTextBytes, err := RawURLEncoding.DecodeString(string(cipherText))
85+
if err != nil {
86+
return []byte(""), err
87+
}
88+
89+
block, err := aes.NewCipher(keyBytes)
90+
if err != nil {
91+
return []byte(""), err
92+
}
93+
94+
aesGCM, err := cipher.NewGCM(block)
95+
if err != nil {
96+
return []byte(""), err
97+
}
98+
99+
nonceSize := aesGCM.NonceSize()
100+
if len(cipherTextBytes) < nonceSize {
101+
return []byte(""), fmt.Errorf("ciphertext too short")
102+
}
103+
104+
nonce, cipherTextBytes := cipherTextBytes[:nonceSize], cipherTextBytes[nonceSize:]
105+
plainTextBytes, err := aesGCM.Open(nil, nonce, cipherTextBytes, nil)
106+
if err != nil {
107+
return []byte(""), err
108+
}
109+
110+
return plainTextBytes, nil
111+
}

src/js/src/firetty.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import * as firebase from 'firebase';
1+
import * as firebase from 'firebase/app';
2+
import 'firebase/database';
23
import { Terminal, eventHandler, eventhandlertype, CloserArgs} from "./webtty";
34

45

src/resources/chat-widget/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
node_modules/

src/resources/chat-widget/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Rowy
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)