diff --git a/README.md b/README.md
index ee35ad5ed..787aa6966 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,7 @@ Currently, this project is only functional with the `text-davinci-003` model.
- [Docker](#docker)
- [Access Tokens](#access-tokens)
- [Proxy](#proxy)
+ - [User System](#user-system)
- [Updating](#updating)
- [Use Cases](#use-cases)
- [Origin](#origin)
@@ -235,6 +236,36 @@ set in docker-compose.yml file, under services - api - environment
+### User System
+
+By default, there is no user system enabled, so anyone can access your server.
+
+**This project is not designed to provide a complete and full-featured user system.** It's not high priority task and might never be provided.
+
+[wtlyu](https://github.com/wtlyu) provide a sample user system structure, that you can implement your own user system. It's simple and not a ready-for-use edition.
+
+(If you want to implement your user system, open this ↓)
+
+
+Implement your own user system
+
+To enable the user system, set `ENABLE_USER_SYSTEM=1` in your `.env` file.
+
+The sample structure is simple. It provide three basic endpoint:
+
+1. `/auth/login` will redirect to your own login url. In the sample code, it's `/auth/your_login_page`.
+2. `/auth/logout` will redirect to your own logout url. In the sample code, it's `/auth/your_login_page/logout`.
+3. `/api/me` will return the userinfo: `{ username, display }`.
+ 1. `username` will be used in db, used to distinguish between users.
+ 2. `display` will be displayed in UI.
+
+The only one thing that drive user system work is `req.session.user`. Once it's set, the client will be trusted. Set to `null` if logout.
+
+Please refer to `/api/server/routes/authYoutLogin.js` file. It's very clear and simple to tell you how to implement your user system.
+
+
+
+
### Updating
- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data.
diff --git a/api/.env.example b/api/.env.example
index e8c52c3b0..adf758808 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -20,3 +20,10 @@ MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone"
OPENAI_KEY=
CHATGPT_TOKEN=
BING_TOKEN=
+
+# User System
+
+# global enable/disable the sample user system.
+# this is not a ready to use user system.
+# dont't use it, unless you can write your own code.
+ENABLE_USER_SYSTEM=
diff --git a/api/models/Conversation.js b/api/models/Conversation.js
index 45834379e..7506fafe4 100644
--- a/api/models/Conversation.js
+++ b/api/models/Conversation.js
@@ -43,6 +43,9 @@ const convoSchema = mongoose.Schema(
type: String,
required: true
},
+ user: {
+ type: String
+ },
suggestions: [{ type: String }],
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }]
},
@@ -52,9 +55,9 @@ const convoSchema = mongoose.Schema(
const Conversation =
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
-const getConvo = async (conversationId) => {
+const getConvo = async (user, conversationId) => {
try {
- return await Conversation.findOne({ conversationId }).exec();
+ return await Conversation.findOne({ user, conversationId }).exec();
} catch (error) {
console.log(error);
return { message: 'Error getting single conversation' };
@@ -62,12 +65,13 @@ const getConvo = async (conversationId) => {
};
module.exports = {
- saveConvo: async ({ conversationId, newConversationId, title, ...convo }) => {
+ saveConvo: async (user, { conversationId, newConversationId, title, ...convo }) => {
try {
const messages = await getMessages({ conversationId });
const update = { ...convo, messages };
if (title) {
update.title = title;
+ update.user = user
}
if (newConversationId) {
update.conversationId = newConversationId;
@@ -82,7 +86,7 @@ module.exports = {
}
return await Conversation.findOneAndUpdate(
- { conversationId },
+ { conversationId: conversationId, user },
{ $set: update },
{ new: true, upsert: true }
).exec();
@@ -91,9 +95,9 @@ module.exports = {
return { message: 'Error saving conversation' };
}
},
- updateConvo: async ({ conversationId, ...update }) => {
+ updateConvo: async (user, { conversationId, ...update }) => {
try {
- return await Conversation.findOneAndUpdate({ conversationId }, update, {
+ return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, {
new: true
}).exec();
} catch (error) {
@@ -101,12 +105,11 @@ module.exports = {
return { message: 'Error updating conversation' };
}
},
- // getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(),
- getConvosByPage: async (pageNumber = 1, pageSize = 12) => {
+ getConvosByPage: async (user, pageNumber = 1, pageSize = 12) => {
try {
- const totalConvos = (await Conversation.countDocuments()) || 1;
+ const totalConvos = (await Conversation.countDocuments({ user })) || 1;
const totalPages = Math.ceil(totalConvos / pageSize);
- const convos = await Conversation.find()
+ const convos = await Conversation.find({ user })
.sort({ createdAt: -1, created: -1 })
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
@@ -119,17 +122,17 @@ module.exports = {
}
},
getConvo,
- getConvoTitle: async (conversationId) => {
+ getConvoTitle: async (user, conversationId) => {
try {
- const convo = await getConvo(conversationId);
+ const convo = await getConvo(user, conversationId);
return convo.title;
} catch (error) {
console.log(error);
return { message: 'Error getting conversation title' };
}
},
- deleteConvos: async (filter) => {
- let deleteCount = await Conversation.deleteMany(filter).exec();
+ deleteConvos: async (user, filter) => {
+ let deleteCount = await Conversation.deleteMany({...filter, user}).exec();
deleteCount.messages = await deleteMessages(filter);
return deleteCount;
},
diff --git a/api/models/CustomGpt.js b/api/models/CustomGpt.js
index 1f02bdc4d..af3e09227 100644
--- a/api/models/CustomGpt.js
+++ b/api/models/CustomGpt.js
@@ -12,16 +12,20 @@ const customGptSchema = mongoose.Schema({
type: String,
required: true
},
+ user: {
+ type: String
+ },
}, { timestamps: true });
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
-const createCustomGpt = async ({ chatGptLabel, promptPrefix, value }) => {
+const createCustomGpt = async ({ chatGptLabel, promptPrefix, value, user }) => {
try {
await CustomGpt.create({
chatGptLabel,
promptPrefix,
- value
+ value,
+ user
});
return { chatGptLabel, promptPrefix, value };
} catch (error) {
@@ -31,22 +35,22 @@ const createCustomGpt = async ({ chatGptLabel, promptPrefix, value }) => {
};
module.exports = {
- getCustomGpts: async (filter) => {
+ getCustomGpts: async (user, filter) => {
try {
- return await CustomGpt.find(filter).exec();
+ return await CustomGpt.find({ ...filter, user }).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error getting customGpts' };
}
},
- updateCustomGpt: async ({ value, ...update }) => {
+ updateCustomGpt: async (user, { value, ...update }) => {
try {
- const customGpt = await CustomGpt.findOne({ value }).exec();
+ const customGpt = await CustomGpt.findOne({ value, user }).exec();
if (!customGpt) {
- return await createCustomGpt({ value, ...update });
+ return await createCustomGpt({ value, ...update, user });
} else {
- return await CustomGpt.findOneAndUpdate({ value }, update, {
+ return await CustomGpt.findOneAndUpdate({ value, user }, update, {
new: true,
upsert: true
}).exec();
@@ -56,9 +60,9 @@ module.exports = {
return { message: 'Error updating customGpt' };
}
},
- updateByLabel: async ({ prevLabel, ...update }) => {
+ updateByLabel: async (user, { prevLabel, ...update }) => {
try {
- return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel }, update, {
+ return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel, user }, update, {
new: true,
upsert: true
}).exec();
@@ -67,9 +71,9 @@ module.exports = {
return { message: 'Error updating customGpt' };
}
},
- deleteCustomGpts: async (filter) => {
+ deleteCustomGpts: async (user, filter) => {
try {
- return await CustomGpt.deleteMany(filter).exec();
+ return await CustomGpt.deleteMany({ ...filter, user }).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error deleting customGpts' };
diff --git a/api/package-lock.json b/api/package-lock.json
index f934f00b8..5432865ca 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -12,9 +12,11 @@
"@keyv/mongo": "^2.1.8",
"@vscode/vscode-languagedetection": "^1.0.22",
"@waylaidwanderer/chatgpt-api": "^1.28.2",
+ "axios": "^1.3.4",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
+ "express-session": "^1.17.3",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
"lodash": "^4.17.21",
@@ -1776,11 +1778,13 @@
}
},
"node_modules/axios": {
- "version": "0.26.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
- "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
+ "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"dependencies": {
- "follow-redirects": "^1.14.8"
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
@@ -2452,6 +2456,45 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-session": {
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
+ "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
+ "dependencies": {
+ "cookie": "0.4.2",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.0.2",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "5.2.1",
+ "uid-safe": "~2.1.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express-session/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express-session/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -3507,6 +3550,14 @@
"node": ">= 0.8"
}
},
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@@ -3530,6 +3581,14 @@
"form-data": "^4.0.0"
}
},
+ "node_modules/openai/node_modules/axios": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "dependencies": {
+ "follow-redirects": "^1.14.8"
+ }
+ },
"node_modules/ora": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz",
@@ -3738,6 +3797,11 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -3771,6 +3835,14 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
},
+ "node_modules/random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -4353,6 +4425,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "dependencies": {
+ "random-bytes": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -6152,11 +6235,13 @@
}
},
"axios": {
- "version": "0.26.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
- "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
+ "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"requires": {
- "follow-redirects": "^1.14.8"
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
}
},
"balanced-match": {
@@ -6628,6 +6713,41 @@
}
}
},
+ "express-session": {
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
+ "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
+ "requires": {
+ "cookie": "0.4.2",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.0.2",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "5.2.1",
+ "uid-safe": "~2.1.5"
+ },
+ "dependencies": {
+ "cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -7397,6 +7517,11 @@
"ee-first": "1.1.1"
}
},
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+ },
"onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@@ -7412,6 +7537,16 @@
"requires": {
"axios": "^0.26.0",
"form-data": "^4.0.0"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "requires": {
+ "follow-redirects": "^1.14.8"
+ }
+ }
}
},
"ora": {
@@ -7569,6 +7704,11 @@
"ipaddr.js": "1.9.1"
}
},
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -7593,6 +7733,11 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
},
+ "random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="
+ },
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -8033,6 +8178,14 @@
"mime-types": "~2.1.24"
}
},
+ "uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "requires": {
+ "random-bytes": "~1.0.0"
+ }
+ },
"undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
diff --git a/api/package.json b/api/package.json
index 561908fad..8311456f4 100644
--- a/api/package.json
+++ b/api/package.json
@@ -22,9 +22,11 @@
"@keyv/mongo": "^2.1.8",
"@vscode/vscode-languagedetection": "^1.0.22",
"@waylaidwanderer/chatgpt-api": "^1.28.2",
+ "axios": "^1.3.4",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
+ "express-session": "^1.17.3",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
"lodash": "^4.17.21",
diff --git a/api/server/index.js b/api/server/index.js
index bda88b14f..102811b60 100644
--- a/api/server/index.js
+++ b/api/server/index.js
@@ -1,4 +1,5 @@
const express = require('express');
+const session = require('express-session')
const dbConnect = require('../models/dbConnect');
const { migrateDb } = require('../models');
const path = require('path');
@@ -7,6 +8,7 @@ const routes = require('./routes');
const app = express();
const port = process.env.PORT || 3080;
const host = process.env.HOST || 'localhost'
+const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
const projectPath = path.join(__dirname, '..', '..', 'client');
dbConnect().then(() => {
console.log('Connected to MongoDB');
@@ -16,17 +18,38 @@ dbConnect().then(() => {
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(projectPath, 'public')));
+app.set('trust proxy', 1) // trust first proxy
+app.use(session({
+ secret: 'chatgpt-clone-random-secrect',
+ resave: false,
+ saveUninitialized: true,
+}))
-app.get('/', function (req, res) {
+app.get('/', routes.authenticatedOrRedirect, function (req, res) {
console.log(path.join(projectPath, 'public', 'index.html'));
res.sendFile(path.join(projectPath, 'public', 'index.html'));
});
-app.use('/api/ask', routes.ask);
-app.use('/api/messages', routes.messages);
-app.use('/api/convos', routes.convos);
-app.use('/api/customGpts', routes.customGpts);
-app.use('/api/prompts', routes.prompts);
+app.get('/api/me', function (req, res) {
+ if (userSystemEnabled) {
+ const user = req?.session?.user
+
+ if (user)
+ res.send(JSON.stringify({username: user?.username, display: user?.display}));
+ else
+ res.send(JSON.stringify(null));
+ } else {
+ res.send(JSON.stringify({username: 'anonymous_user', display: 'Anonymous User'}));
+ }
+});
+
+app.use('/api/ask', routes.authenticatedOr401, routes.ask);
+app.use('/api/messages', routes.authenticatedOr401, routes.messages);
+app.use('/api/convos', routes.authenticatedOr401, routes.convos);
+app.use('/api/customGpts', routes.authenticatedOr401, routes.customGpts);
+app.use('/api/prompts', routes.authenticatedOr401, routes.prompts);
+app.use('/auth', routes.auth);
+
app.listen(port, host, () => {
if (host=='0.0.0.0')
diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js
index ad2a7178c..f016a49b3 100644
--- a/api/server/routes/ask.js
+++ b/api/server/routes/ask.js
@@ -37,7 +37,7 @@ router.post('/', async (req, res) => {
});
await saveMessage(userMessage);
- await saveConvo({ ...userMessage, model, ...convo });
+ await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
return await ask({
userMessage,
@@ -176,9 +176,9 @@ const ask = async ({
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
await saveMessage(gptResponse);
- await saveConvo(gptResponse);
+ await saveConvo(req?.session?.user?.username, gptResponse);
sendMessage(res, {
- title: await getConvoTitle(conversationId),
+ title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: gptResponse
@@ -188,10 +188,13 @@ const ask = async ({
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response: gptResponse });
- await saveConvo({
- conversationId,
- title
- });
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId,
+ title
+ }
+ );
}
} catch (error) {
console.log(error);
diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js
index 981cd5617..0614faf86 100644
--- a/api/server/routes/askBing.js
+++ b/api/server/routes/askBing.js
@@ -38,7 +38,7 @@ router.post('/', async (req, res) => {
});
await saveMessage(userMessage);
- await saveConvo({ ...userMessage, model, ...convo });
+ await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
return await ask({
isNewConversation,
@@ -108,10 +108,13 @@ const ask = async ({
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
if (conversationId != userMessage.conversationId && isNewConversation)
- await saveConvo({
- conversationId: conversationId,
- newConversationId: userMessage.conversationId
- });
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId: conversationId,
+ newConversationId: userMessage.conversationId
+ }
+ );
conversationId = userMessage.conversationId;
response.text = response.response;
@@ -129,9 +132,10 @@ const ask = async ({
response.text = await handleText(response, true);
await saveMessage(response);
- await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
+ await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
+
sendMessage(res, {
- title: await getConvoTitle(conversationId),
+ title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: response
@@ -141,10 +145,13 @@ const ask = async ({
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response });
- await saveConvo({
- conversationId,
- title
- });
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId,
+ title
+ }
+ );
}
} catch (error) {
console.log(error);
diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js
index 00f287fa7..a2017cf7d 100644
--- a/api/server/routes/askSydney.js
+++ b/api/server/routes/askSydney.js
@@ -38,7 +38,7 @@ router.post('/', async (req, res) => {
});
await saveMessage(userMessage);
- await saveConvo({ ...userMessage, model, ...convo });
+ await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
return await ask({
isNewConversation,
@@ -132,18 +132,22 @@ const ask = async ({
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
if (conversationId != userMessage.conversationId && isNewConversation)
- await saveConvo({
- conversationId: conversationId,
- newConversationId: userMessage.conversationId
- });
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId: conversationId,
+ newConversationId: userMessage.conversationId
+ }
+ );
conversationId = userMessage.conversationId;
response.text = await handleText(response, true);
// Save sydney response & convo, then send
await saveMessage(response);
- await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
+ await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
+
sendMessage(res, {
- title: await getConvoTitle(conversationId),
+ title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: response
@@ -153,10 +157,13 @@ const ask = async ({
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response });
- await saveConvo({
- conversationId,
- title
- });
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId,
+ title
+ }
+ );
}
} catch (error) {
console.log(error);
diff --git a/api/server/routes/auth.js b/api/server/routes/auth.js
new file mode 100644
index 000000000..b4d986c48
--- /dev/null
+++ b/api/server/routes/auth.js
@@ -0,0 +1,46 @@
+const express = require('express');
+const router = express.Router();
+const authYourLogin = require('./authYourLogin');
+const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
+
+router.get('/login', function (req, res) {
+ if (userSystemEnabled)
+ res.redirect('/auth/your_login_page')
+ else
+ res.redirect('/')
+})
+
+router.get('/logout', function (req, res) {
+ // clear the session
+ req.session.user = null
+
+ req.session.save(function (error) {
+ if (userSystemEnabled)
+ res.redirect('/auth/your_login_page/logout')
+ else
+ res.redirect('/')
+ })
+})
+
+const authenticatedOr401 = (req, res, next) => {
+ if (userSystemEnabled) {
+ const user = req?.session?.user;
+
+ if (user) next();
+ else res.status(401).end();
+ } else next();
+}
+
+const authenticatedOrRedirect = (req, res, next) => {
+ if (userSystemEnabled) {
+ const user = req?.session?.user;
+
+ if (user) next();
+ else res.redirect('/auth/login').end();
+ } else next();
+}
+
+if (userSystemEnabled)
+ router.use('/your_login_page', authYourLogin);
+
+module.exports = { router, authenticatedOr401, authenticatedOrRedirect };
diff --git a/api/server/routes/authYourLogin.js b/api/server/routes/authYourLogin.js
new file mode 100644
index 000000000..c2320fbb3
--- /dev/null
+++ b/api/server/routes/authYourLogin.js
@@ -0,0 +1,40 @@
+const express = require('express');
+const router = express.Router();
+
+// WARNING!
+// THIS IS NOT A READY TO USE USER SYSTEM
+// PLEASE IMPLEMENT YOUR OWN USER SYSTEM
+
+const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
+
+// Logout
+router.get('/logout', (req, res) => {
+ // Do anything you want
+ console.warn('logout not implemented!')
+
+ // finish
+ res.redirect('/')
+});
+
+// Login
+router.get('/', async (req, res) => {
+ // Do anything you want
+ console.warn('login not implemented! Automatic passed as sample user')
+
+ // save the user info into session
+ // username will be used in db
+ // display will be used in UI
+ req.session.user = {
+ username: 'sample_user',
+ display: 'Sample User',
+ }
+
+ req.session.save(function (error) {
+ if (error) {
+ console.log(error);
+ res.send(`
Login Failed. An error occurred. Please see the server logs for details.
`);
+ } else res.redirect('/')
+ })
+});
+
+module.exports = router;
diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js
index 99fb82470..0364684fa 100644
--- a/api/server/routes/convos.js
+++ b/api/server/routes/convos.js
@@ -1,10 +1,39 @@
const express = require('express');
const router = express.Router();
+const { titleConvo } = require('../../app/');
+const { getConvo, saveConvo, getConvoTitle } = require('../../models');
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
+const { getMessages } = require('../../models/Message');
router.get('/', async (req, res) => {
const pageNumber = req.query.pageNumber || 1;
- res.status(200).send(await getConvosByPage(pageNumber));
+ res.status(200).send(await getConvosByPage(req?.session?.user?.username, pageNumber));
+});
+
+router.post('/gen_title', async (req, res) => {
+ const { conversationId } = req.body.arg;
+
+ const convo = await getConvo(req?.session?.user?.username, conversationId)
+ const firstMessage = (await getMessages({ conversationId }))[0]
+ const secondMessage = (await getMessages({ conversationId }))[1]
+
+ const title = convo.jailbreakConversationId
+ ? await getConvoTitle(req?.session?.user?.username, conversationId)
+ : await titleConvo({
+ model: convo?.model,
+ message: firstMessage?.text,
+ response: JSON.stringify(secondMessage?.text || '')
+ });
+
+ await saveConvo(
+ req?.session?.user?.username,
+ {
+ conversationId,
+ title
+ }
+ )
+
+ res.status(200).send(title);
});
router.post('/clear', async (req, res) => {
@@ -15,7 +44,7 @@ router.post('/clear', async (req, res) => {
}
try {
- const dbResponse = await deleteConvos(filter);
+ const dbResponse = await deleteConvos(req?.session?.user?.username, filter);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
@@ -27,7 +56,7 @@ router.post('/update', async (req, res) => {
const update = req.body.arg;
try {
- const dbResponse = await updateConvo(update);
+ const dbResponse = await updateConvo(req?.session?.user?.username, update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
diff --git a/api/server/routes/customGpts.js b/api/server/routes/customGpts.js
index a58143a8b..d430b39ad 100644
--- a/api/server/routes/customGpts.js
+++ b/api/server/routes/customGpts.js
@@ -8,7 +8,7 @@ const {
} = require('../../models');
router.get('/', async (req, res) => {
- const models = (await getCustomGpts()).map((model) => {
+ const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
model = model.toObject();
model._id = model._id.toString();
return model;
@@ -20,8 +20,8 @@ router.post('/delete', async (req, res) => {
const { arg } = req.body;
try {
- await deleteCustomGpts(arg);
- const models = (await getCustomGpts()).map((model) => {
+ await deleteCustomGpts(req?.session?.user?.username, arg);
+ const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
model = model.toObject();
model._id = model._id.toString();
return model;
@@ -56,7 +56,7 @@ router.post('/', async (req, res) => {
}
try {
- const dbResponse = await setter(update);
+ const dbResponse = await setter(req?.session?.user?.username, update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
diff --git a/api/server/routes/index.js b/api/server/routes/index.js
index 235d08da2..5164badc3 100644
--- a/api/server/routes/index.js
+++ b/api/server/routes/index.js
@@ -3,5 +3,6 @@ const messages = require('./messages');
const convos = require('./convos');
const customGpts = require('./customGpts');
const prompts = require('./prompts');
+const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth');
-module.exports = { ask, messages, convos, customGpts, prompts };
\ No newline at end of file
+module.exports = { ask, messages, convos, customGpts, prompts, auth, authenticatedOr401, authenticatedOrRedirect };
\ No newline at end of file
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 5f1446fd0..0421d14d9 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -5,34 +5,67 @@ import TextChat from './components/Main/TextChat';
import Nav from './components/Nav';
import MobileNav from './components/Nav/MobileNav';
import useDocumentTitle from '~/hooks/useDocumentTitle';
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
+import { setUser } from './store/userReducer';
+import axios from 'axios'
const App = () => {
+ const dispatch = useDispatch();
+
const { messages, messageTree } = useSelector((state) => state.messages);
+ const { user } = useSelector((state) => state.user);
const { title } = useSelector((state) => state.convo);
const { conversationId } = useSelector((state) => state.convo);
const [ navVisible, setNavVisible ]= useState(false)
useDocumentTitle(title);
- return (
-
-
-
-
-
- {messages.length === 0 ? (
-
- ) : (
-
- )}
-
+ useEffect(() => {
+ axios.get('/api/me', {
+ timeout: 1000,
+ withCredentials: true
+ }).then((res) => {
+ return res.data
+ }).then((user) => {
+ if (user)
+ dispatch(setUser(user))
+ else {
+ console.log('Not login!')
+ window.location.href = "/auth/login";
+ }
+ }).catch((error) => {
+ console.error(error)
+ console.log('Not login!')
+ window.location.href = "/auth/login";
+ })
+ // setUser
+ }, [])
+
+ if (user)
+ return (
+
+
+
+
+
+ {messages.length === 0 ? (
+
+ ) : (
+
+ )}
+
+
-
- );
+ );
+ else
+ return (
+
+
+
+ )
};
export default App;
diff --git a/client/src/components/Main/Landing.jsx b/client/src/components/Main/Landing.jsx
index 4c04b9856..594655171 100644
--- a/client/src/components/Main/Landing.jsx
+++ b/client/src/components/Main/Landing.jsx
@@ -28,7 +28,7 @@ export default function Landing({ title }) {
return (
-
+
ChatGPT Clone
diff --git a/client/src/components/Main/SubmitButton.jsx b/client/src/components/Main/SubmitButton.jsx
index ed570f5c7..1aa437e26 100644
--- a/client/src/components/Main/SubmitButton.jsx
+++ b/client/src/components/Main/SubmitButton.jsx
@@ -10,9 +10,12 @@ export default function SubmitButton({ submitMessage }) {
if (isSubmitting) {
return (
-