diff --git a/.gitignore b/.gitignore
index fd7a688..3cfc8d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@
.vscode/
node_modules/
.env
-*.env
\ No newline at end of file
+*.env
+repo_*
\ No newline at end of file
diff --git a/services/client/src/App.vue b/services/client/src/App.vue
index 28dcdeb..ec7de01 100644
--- a/services/client/src/App.vue
+++ b/services/client/src/App.vue
@@ -29,6 +29,11 @@
>GitHub
+
+ 3Bot
+
Logout
-
-
-
-
-
-
diff --git a/services/client/src/main.js b/services/client/src/main.js
index e181884..9f96b02 100644
--- a/services/client/src/main.js
+++ b/services/client/src/main.js
@@ -36,3 +36,13 @@ new Vue({
store,
render: (h) => h(App),
}).$mount("#app");
+
+// Does it help?
+const allowCrossDomain = function (req, res, next) {
+ res.header("Access-Control-Allow-Origin", "*");
+ res.header("Access-Control-Allow-Methods", "*");
+ res.header("Access-Control-Allow-Headers", "*");
+ next();
+};
+
+Vue.use(allowCrossDomain);
diff --git a/services/client/src/router/index.js b/services/client/src/router/index.js
index 37f3f91..dfbbeaa 100644
--- a/services/client/src/router/index.js
+++ b/services/client/src/router/index.js
@@ -6,6 +6,7 @@ import Todo from "@/views/Todo.vue";
import Login from "@/views/Login.vue";
import Register from "@/views/Register.vue";
import Github from "@/views/Github.vue";
+import Tribot from "@/views/Tribot.vue";
import Logout from "@/views/Logout.vue";
Vue.use(VueRouter);
@@ -21,9 +22,9 @@ const routes = [
path: "/todo",
name: "Todo",
component: Todo,
- // meta: {
- // requiresAuth: true,
- // },
+ meta: {
+ requiresAuth: true,
+ },
},
{
path: "/login",
@@ -49,6 +50,14 @@ const routes = [
requiresVisitor: true,
},
},
+ {
+ path: "/tribot",
+ name: "Tribot",
+ component: Tribot,
+ meta: {
+ requiresVisitor: true,
+ },
+ },
{
path: "/logout",
name: "Logout",
diff --git a/services/client/src/store/index.js b/services/client/src/store/index.js
index 53b1d4e..6a1bc47 100644
--- a/services/client/src/store/index.js
+++ b/services/client/src/store/index.js
@@ -8,7 +8,7 @@ export default new Vuex.Store({
state: {
token: localStorage.getItem("access_token") || null,
// access_token_cookie: Vue.$cookies.get("access_token_cookie") || null,
- api_url: "http://localhost:8080",
+ api_url: "http://127.0.0.1:8080",
},
mutations: {
updateToken(state, token) {
diff --git a/services/client/src/views/Home.vue b/services/client/src/views/Home.vue
index 12175cb..22be9bb 100644
--- a/services/client/src/views/Home.vue
+++ b/services/client/src/views/Home.vue
@@ -7,7 +7,21 @@
diff --git a/services/client/src/views/Logout.vue b/services/client/src/views/Logout.vue
index cc9d175..7afc230 100644
--- a/services/client/src/views/Logout.vue
+++ b/services/client/src/views/Logout.vue
@@ -6,7 +6,9 @@
import Vue from "vue";
import axios from "axios";
import VueAxios from "vue-axios";
+import VueCookie from "vue-cookie";
+Vue.use(VueCookie);
Vue.use(VueAxios, axios);
export default {
name: "Logout",
@@ -16,16 +18,30 @@ export default {
};
},
created() {
- this.distroyToken();
+ this.logout();
},
methods: {
+ logout() {
+ // remove token from cookie
+ this.$cookie.delete("access_token_cookie");
+
+ // remove token from localStorage
+ localStorage.clear(); // not the best practice but workes for now
+
+ // remove the token from store state
+ this.$store.commit("distroyToken");
+
+ // redirect
+ this.$router.push({ name: "Login" });
+ },
+
distroyToken() {
const path = this.baseUrl + "/auth/logout";
- axios.defaults.headers.common["Authorization"] =
+ Vue.axios.defaults.headers.common["Authorization"] =
"Bearer " + this.$store.state.token;
- axios.get(path).then((response) => {
+ Vue.axios.get(path).then((response) => {
// remove the cookie from server
console.log(response.data.message);
diff --git a/services/client/src/views/Todo.vue b/services/client/src/views/Todo.vue
index c1a88cf..b9fa1bb 100644
--- a/services/client/src/views/Todo.vue
+++ b/services/client/src/views/Todo.vue
@@ -60,13 +60,13 @@ Vue.use(VueAxios, axios);
export default {
name: "Todo",
beforeCreate() {
- var token = this.$cookie.get("access_token_cookie");
- console.log(token);
- // add to local storage
- localStorage.setItem("access_token", token); // token stores in cookies
+ var token = this.$cookie.get("access_token_cookie");
+ console.log(token);
+ // add to local storage
+ localStorage.setItem("access_token", token); // token stores in cookies
- // update the store state token
- this.$store.commit("updateToken", token);
+ // update the store state token
+ this.$store.commit("updateToken", token);
},
data() {
@@ -86,7 +86,6 @@ export default {
},
},
-
created() {
// this.loadToken();
this.getTodos();
diff --git a/services/client/src/views/Tribot.vue b/services/client/src/views/Tribot.vue
new file mode 100644
index 0000000..6681433
--- /dev/null
+++ b/services/client/src/views/Tribot.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/client/vue.config.js b/services/client/vue.config.js
index 7dcefbb..c69132f 100644
--- a/services/client/vue.config.js
+++ b/services/client/vue.config.js
@@ -1,5 +1,5 @@
module.exports = {
devServer: {
- proxy: 'http://127.0.0.1:5000',
+ proxy: 'https://127.0.0.1:5000',
}
}
\ No newline at end of file
diff --git a/services/server/3bot/proxy_oauth.py b/services/server/3bot/proxy_oauth.py
new file mode 100644
index 0000000..c1d8011
--- /dev/null
+++ b/services/server/3bot/proxy_oauth.py
@@ -0,0 +1,70 @@
+from flask import Flask, request, json, session, redirect
+from beaker.middleware import SessionMiddleware
+from urllib.parse import urlencode
+from uuid import uuid4
+import requests
+import os
+
+
+app = Flask(__name__)
+app.config["SECRET_KEY"] = "secret"
+
+
+# APIs URL
+PROXY_OAUTH_SERVER_URL = "http://127.0.0.1:9000"
+HOST_URL = 'https://login.threefold.me'
+
+# App info
+APP_ID = '127.0.0.1:5000'
+REDIRECT_ROUTE = "/callback"
+
+def generate_login_url():
+
+ state = str(uuid4()).replace("-", "")
+ session["state"] = state
+
+ response = requests.get(f"{PROXY_OAUTH_SERVER_URL}/pubkey")
+ response.raise_for_status() # will raise an HTTPError if the HTTP request returned an unsuccessful status code
+ data = response.json()
+ pubkey = data["publickey"].encode()
+
+ params = {
+ "state": state,
+ "appid": APP_ID,
+ "scope": json.dumps({"user": True, "email": True}),
+ "redirecturl": REDIRECT_ROUTE,
+ "publickey": pubkey,
+ }
+
+ url_params = urlencode(params)
+ return f"{HOST_URL}?{url_params}"
+
+@app.route("/login")
+def login():
+ return redirect(generate_login_url())
+
+@app.route(REDIRECT_ROUTE)
+def callback():
+ state = session["state"]
+ signed_attempt_val = request.args.get("signedAttempt")
+
+ data = {
+ "signedAttempt": signed_attempt_val,
+ "state": state
+ }
+
+ response = requests.post(f"{PROXY_OAUTH_SERVER_URL}/verify", data=data)
+ response.raise_for_status()
+
+ return response.json()
+
+# session_opts = {
+# 'session.type': 'file',
+# "session.data_dir": "./data",
+# "session.auto": True
+# }
+
+# wsgi_app = SessionMiddleware(app, session_opts)
+
+if __name__ == '__main__':
+ app.run(debug=True)
\ No newline at end of file
diff --git a/services/server/3bot/proxy_server.py b/services/server/3bot/proxy_server.py
new file mode 100644
index 0000000..3e2f5d5
--- /dev/null
+++ b/services/server/3bot/proxy_server.py
@@ -0,0 +1,150 @@
+import nacl.encoding
+import nacl.exceptions
+import nacl.signing
+import requests
+import json
+import base64
+
+from bottle import Bottle, request, abort, response
+from nacl.public import Box
+
+
+PUBKEY_URL = "/pubkey"
+VERIFY_URL = "/verify"
+KEY_PATH = "/opt/priv.key"
+
+
+with open(KEY_PATH) as kp:
+ PRIV_KEY = nacl.signing.SigningKey(kp.read(), encoder=nacl.encoding.Base64Encoder)
+
+app = application = Bottle()
+
+
+def enable_cors(fn):
+ def _enable_cors(*args, **kwargs):
+ # set CORS headers
+ response.headers["Access-Control-Allow-Origin"] = "*"
+ response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS, DELETE"
+ response.headers[
+ "Access-Control-Allow-Headers"
+ ] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
+
+ return fn(*args, **kwargs)
+
+ return _enable_cors
+
+
+@app.route("/checks/readiness")
+def readiness():
+ with open("/opt/priv.key", "r") as f:
+ priv_key = f.read()
+ if priv_key:
+ return {"readiness": "success"}
+ else:
+ return abort(400, "Private key not created")
+
+
+@app.route("/checks/liveness")
+def liveness():
+ return {"liveness": "success"}
+
+
+@app.route(PUBKEY_URL)
+@enable_cors
+def pubkey():
+ public_key = PRIV_KEY.verify_key
+
+ return {"publickey": public_key.to_curve25519_public_key().encode(encoder=nacl.encoding.Base64Encoder).decode()}
+
+
+@app.post(VERIFY_URL)
+@enable_cors
+def verify():
+
+ is_json = "application/json" in request.headers["Content-Type"]
+ if is_json:
+ request_data = request.json
+ else:
+ request_data = request.params
+
+ data = request_data.get("signedAttempt")
+ state = request_data.get("state")
+
+ if not data:
+ return abort(400, "signedAttempt parameter is missing")
+
+ if not is_json:
+ try:
+ data = json.loads(data)
+ except json.JSONDecodeError:
+ return abort(400, "signedAttempt not in correct format")
+
+ if "signedAttempt" not in data:
+ return abort(400, "signedAttempt value is missing")
+
+ username = data.get("doubleName")
+
+ if not username:
+ return abort(400, "DoubleName is missing")
+
+ res = requests.get(f"https://login.threefold.me/api/users/{username}", {"Content-Type": "application/json"})
+ if res.status_code != 200:
+ return abort(400, "Error getting user pub key")
+
+ pub_key = nacl.signing.VerifyKey(res.json()["publicKey"], encoder=nacl.encoding.Base64Encoder)
+
+ # verify data
+ signedData = data["signedAttempt"]
+
+ verifiedData = pub_key.verify(base64.b64decode(signedData)).decode()
+
+ data = json.loads(verifiedData)
+
+ if "doubleName" not in data:
+ return abort(400, "Decrypted data does not contain (doubleName)")
+
+ if "signedState" not in data:
+ return abort(400, "Decrypted data does not contain (state)")
+
+ if data["doubleName"] != username:
+ return abort(400, "username mismatch!")
+
+ # verify state
+ signed_state = data.get("signedState", "")
+ if state != signed_state:
+ return abort(400, "Invalid state. not matching one in user session")
+
+ nonce = base64.b64decode(data["data"]["nonce"])
+ ciphertext = base64.b64decode(data["data"]["ciphertext"])
+
+ try:
+ box = Box(PRIV_KEY.to_curve25519_private_key(), pub_key.to_curve25519_public_key())
+ decrypted = box.decrypt(ciphertext, nonce)
+ except nacl.exceptions.CryptoError:
+ return abort(400, "Error decrypting data")
+
+ try:
+ result = json.loads(decrypted)
+ except json.JSONDecodeError:
+ return abort(400, "3bot login returned faulty data")
+
+ if "email" not in result:
+ return abort(400, "Email is not present in data")
+
+ email = result["email"]["email"]
+
+ sei = result["email"]["sei"]
+ res = requests.post(
+ "https://openkyc.live/verification/verify-sei",
+ headers={"Content-Type": "application/json"},
+ json={"signedEmailIdentifier": sei},
+ )
+
+ if res.status_code != 200:
+ return abort(400, "Email is not verified")
+
+ return {"email": email, "username": username}
+
+
+if __name__ == "__main__":
+ app.run(port=9000)
diff --git a/services/server/3bot/tfl_auth.py b/services/server/3bot/tfl_auth.py
new file mode 100644
index 0000000..b1321c5
--- /dev/null
+++ b/services/server/3bot/tfl_auth.py
@@ -0,0 +1,78 @@
+import json
+from flask import Flask, redirect, request
+from ThreefoldLoginPkg import ThreefoldLogin
+from repo.ThreefoldLoginPkg.threefold_login import ThreefoldLogin
+import string
+from urllib.parse import urlencode
+import random
+
+
+app = Flask(__name__)
+
+
+def generate_authenticator():
+ api_url = 'https://login.threefold.me'
+ kyc_backend_url = 'https://openkyc.staging.jimber.org'
+
+ email_identifier = 'omarabdul3ziz@gmail.com'
+ seed_phrase = "dinner test old limit mass brief desk decline clarify scene strike accident olympic meadow click nuclear avocado outside share excite rookie snow adapt blast"
+
+ app_id = '127.0.0.1:5000'
+ redirect_url = "/callback"
+
+ authenticator = ThreefoldLogin(api_url, app_id, seed_phrase, redirect_url, kyc_backend_url)
+
+ allowed = string.ascii_letters + string.digits
+ state = ''.join(random.SystemRandom().choice(allowed) for _ in range(32))
+ login_url = authenticator.generate_login_url(state)
+
+ callback_url = f"{app_id}{redirect_url}"
+
+ return authenticator, state, login_url, callback_url, email_identifier
+
+AUTHENTICATOR, STATE, LOGIN_URL, CALLBACK_URL, EMAIL_ID = generate_authenticator()
+
+@app.route('/login')
+def login():
+ # DoneTODO: cause a http bad
+ return redirect(LOGIN_URL)
+
+@app.route('/callback')
+def callback():
+ #DoneTODO: get valid callback_url
+
+ query = request.args.get("signedAttempt")
+ params = urlencode({"signedAttempt": query})
+ callback_url = f"http://{CALLBACK_URL}?{params}"
+
+ authenticator = AUTHENTICATOR
+ state = STATE
+ email_id = EMAIL_ID
+
+ msg = ""
+ authenticator.parse_and_validate_redirect_url(callback_url, state)
+ msg = 'successfully validated login attempt'
+
+ resp = authenticator.verify_signed_email_idenfier(email_id)
+
+ # if authenticator.is_email_verified(email_id):
+ # msg = msg + 'email is verified'
+ # else:
+ # msg = msg + 'email is not verified'
+
+ # try:
+ # msg = ""
+ # authenticator.parse_and_validate_redirect_url(callback_url, state)
+ # msg = 'successfully validated login attempt'
+ # if authenticator.is_email_verified(email_id):
+ # msg = msg + 'email is verified'
+ # else:
+ # msg = msg + 'email is not verified'
+ # except ValueError:
+ # msg = 'failed to validate login attempt'
+
+ return resp
+
+
+if __name__=='__main__':
+ app.run(debug=True)
\ No newline at end of file
diff --git a/services/server/3bot/utlis/cors.py b/services/server/3bot/utlis/cors.py
new file mode 100644
index 0000000..f294358
--- /dev/null
+++ b/services/server/3bot/utlis/cors.py
@@ -0,0 +1,10 @@
+from flask import make_response
+
+def enable_cors(func):
+ def decorator(*args, **kwargs):
+ response = make_response()
+ response.headers["Access-Control-Allow-Origin"] = "*"
+ response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS, DELETE"
+ response.headers["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
+ return func(*args, **kwargs)
+ return decorator
\ No newline at end of file
diff --git a/services/server/app.py b/services/server/app.py
index e739afc..edc56b0 100644
--- a/services/server/app.py
+++ b/services/server/app.py
@@ -4,7 +4,7 @@
import os
from api import api
-from auth import github_blueprint, auth_blueprint, JWTManager
+from auth import github_blueprint, auth_blueprint, tribot_bp, JWTManager
from model import DATABASE_URL
@@ -17,7 +17,7 @@
SECRET_KEY = os.getenv("SECRET_KEY")
-app.config['SECRET_KEY'] = SECRET_KEY
+app.config['SECRET_KEY'] = "SECRET_KEY"
app.config["MONGO_URI"] = DATABASE_URL
@@ -31,3 +31,4 @@
app.register_blueprint(github_blueprint, url_prefix='/login')
app.register_blueprint(auth_blueprint, url_prefix='/auth')
+app.register_blueprint(tribot_bp, url_prefix='/3bot')
diff --git a/services/server/auth.py b/services/server/auth.py
index 1188d0e..f3403b1 100644
--- a/services/server/auth.py
+++ b/services/server/auth.py
@@ -1,7 +1,9 @@
-from flask import Flask, request, jsonify, make_response, redirect, url_for, Blueprint
+from flask import Flask, request, jsonify, make_response, redirect, url_for, Blueprint, json, session
from flask_dance.contrib.github import make_github_blueprint, github
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, JWTManager, unset_jwt_cookies, set_access_cookies
-
+from urllib.parse import urlencode
+from uuid import uuid4
+import requests
import os
from model import users
@@ -11,12 +13,91 @@
auth_blueprint = Blueprint('auth_blueprint', __name__)
+tribot_bp = Blueprint('threebot_blueprint', __name__)
+
+github_blueprint = make_github_blueprint(
+ client_id=GITHUB_ID, client_secret=GITHUB_SECRET)
+
+#########################################
+## ========== 3 bot Auth ========== ##
+#########################################
+
+# APIs URL
+PROXY_OAUTH_SERVER_URL = "http://127.0.0.1:9000"
+HOST_URL = 'https://login.threefold.me'
+
+# App info
+APP_ID = '127.0.0.1:5000'
+REDIRECT_ROUTE = "/3bot/callback"
+
+
+def generate_login_url():
+
+ state = str(uuid4()).replace("-", "")
+ session["state"] = state
+
+ response = requests.get(f"{PROXY_OAUTH_SERVER_URL}/pubkey")
+ # will raise an HTTPError if the HTTP request returned an unsuccessful status code
+ response.raise_for_status()
+ data = response.json()
+ pubkey = data["publickey"].encode()
+
+ params = {
+ "state": state,
+ "appid": APP_ID,
+ "scope": json.dumps({"user": True, "email": True}),
+ "redirecturl": REDIRECT_ROUTE,
+ "publickey": pubkey,
+ }
+
+ url_params = urlencode(params)
+ return f"{HOST_URL}?{url_params}"
+
+
+@tribot_bp.route("/login")
+def tribot_login():
+ return redirect(generate_login_url())
+
+
+@tribot_bp.route("/callback")
+def callback():
+ state = session["state"]
+ signed_attempt_val = request.args.get("signedAttempt")
+
+ data = {
+ "signedAttempt": signed_attempt_val,
+ "state": state
+ }
+
+ response = requests.post(f"{PROXY_OAUTH_SERVER_URL}/verify", data=data)
+ response.raise_for_status()
+
+ account_info = response.json()
+
+ username = account_info['username']
+ # fetch user from db
+ user = users.find_one({'username': username})
-github_blueprint = make_github_blueprint(client_id=GITHUB_ID, client_secret=GITHUB_SECRET )
+ # or create new one
+ if user is None:
+ user = {'username': username,
+ 'password': "",
+ 'admin': False}
+
+ users.insert(user)
+
+ # create and respond with token
+ access_token = create_access_token(identity=username)
+
+ response = make_response(redirect("http://127.0.0.1:8080/"))
+ response.set_cookie("access_token_cookie", access_token)
+ return response
#########################################
-## ============= Auth ============= ##
+## ========= github Auth ========== ##
#########################################
+
+
@auth_blueprint.route('/github')
def github_login():
@@ -37,10 +118,10 @@ def github_login():
'admin': False}
users.insert(user)
-
+
# create and respond with token
access_token = create_access_token(identity=username)
-
+
# default set cookie
response = jsonify(access_token=access_token)
set_access_cookies(response, access_token)
@@ -50,6 +131,10 @@ def github_login():
# response.set_cookie('access_token_cookie', access_token)
return response
+#########################################
+## ========== basic Auth ========== ##
+#########################################
+
@auth_blueprint.route('/register', methods=['POST'])
def register():
@@ -71,7 +156,7 @@ def register():
# create and respond with token
access_token = create_access_token(identity=username)
-
+
# default set cookie
response = jsonify(access_token=access_token)
# set_access_cookies(response, access_token) # make the set from front end
@@ -80,7 +165,8 @@ def register():
# response = make_response(redirect(url_for("index")))
response.set_cookie('access_token_cookie', access_token)
return response
-
+
+
@auth_blueprint.route('/login', methods=['POST'])
def login():
# getting criedentials from BODY
@@ -99,7 +185,7 @@ def login():
return "No such user."
if password != user['password']:
return "Wrong password."
-
+
# create token
access_token = create_access_token(identity=username)
diff --git a/services/server/start_proxy_server.sh b/services/server/start_proxy_server.sh
new file mode 100755
index 0000000..03c35f9
--- /dev/null
+++ b/services/server/start_proxy_server.sh
@@ -0,0 +1,3 @@
+#! /usr/bin/sh
+
+python3 ./3bot/proxy_server.py
\ No newline at end of file
diff --git a/services/server/wsgi.py b/services/server/wsgi.py
old mode 100644
new mode 100755
index 5751813..65317ac
--- a/services/server/wsgi.py
+++ b/services/server/wsgi.py
@@ -1,4 +1,6 @@
+#! /usr/bin/python3
+
from app import app
if __name__ == '__main__':
- app.run(debug=True)
\ No newline at end of file
+ app.run(debug=True, ssl_context=('/home/omar/localhost.pem', '/home/omar/localhost-key.pem'))
\ No newline at end of file