A shitton of documentation

This commit is contained in:
Tracker-Friendly 2024-04-03 12:52:13 +01:00
parent ce0506c09a
commit 7599699755
4 changed files with 791 additions and 0 deletions

View File

@ -25,6 +25,8 @@ You'll need to store two things in local storage:
## OAuth2
RDIR - /login - start the oauth2 login process, see OAuth2.md
POST - /api/auth - interface directly with the burgerauth system, provide "secretKey, appId, code and codemethod"
Code and Codemethod are only used for PKCE, if PKCE is not enabled set the value of both to "none"

152
Oauth2.md Normal file
View File

@ -0,0 +1,152 @@
# Burgerauth as an oauth2 provider
## Endpoints
GET - /.well-known/openid-configuration - OpenID Connect Discovery
GET - /login - Authorization Endpoint
POST - /login/oauth/access_token - Token Exchange Endpoint
POST - /userinfo - OpenID Connect UserInfo
## Supported OAuth2 Grants
Burgerauth supports the authorization code grant standard with support for:
- Proof Key for Code Exchange (PKCE)
- OpenID Connect (OCID)
To use the Authorization Code Grant as a third party application it is required to register a new application in the dashboard (/dashboard)
## Scopes
Burgerauth does not currently support scopes, and by default grants all scopes regarding user infomation
## Client types
Burgerauth supports both confidential and public client types, as defined by RFC 6749.
You can view this spec here: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
## Examples
### Confidential client
This example does not use PKCE.
1. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:
```
/login?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE
```
The CLIENT_ID can be obtained by registering an application in the dashboard. The STATE is a random string that will be sent back to your application after the user authorizes. The state parameter is optional, but should be used to prevent CSRF attacks.
The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the REDIRECT_URL, for example:
```
https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE
```
2. Using the provided code from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with application/json and application/x-www-form-urlencoded body, for example:
POST - /api/tokenauth
```
{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"code": "RETURNED_CODE",
"grant_type": "authorization_code",
"redirect_uri": "REDIRECT_URI" (optional, since redirect_uri is not verified on the server)
}
```
Response:
```
{
"access_token": "(random string)",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "(random string)",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJleGFtcGxlIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmhlY3RhYml0Lm9yZyIsIm5hbWUiOiJleGFtcGxlIiwiYXVkIjoibXlhcHBpZCIsImV4cCI6MCwiaWF0IjozNjAwLCJhdXRoX3RpbWUiOjAsIm5vbmNlIjoiMDYyMGM2NGNjZmE5MzQ0NTczMTQ5NTM5MmNiMzQzYjM2MGJkMjAwYmIzM2QyNTRjNzQ3YWZjYmY1YzlhNDkzYjM3MWE0MWI2Zjk3YzM5ZWI0ZjU0YmJmNDBjYjNkNjhmNTBmNmFhNjMwMmY2ZjRkM2I0NTI4MWVjYzI2ZTQ5MjNiNDFjYTFhYTIzNjIwOTMzNmU4MGZkZjk1ZGUxMGRiMzNhZWJjY2M3MmM2NjU2ZjI4MmUzZTY0ZTFkZDVlMzkxOGJlMmM2MDA4ZmFlZGZiNGVjOGE3NDk5Y2JkMzI1NGNjM2Q0ZGI0ZTMyNjQ4NDgxZDY2Mjg0YmNkMjgyZDVkNGQ3NDg2NjU2NWZjNjhlYTM0ZTIxYmE3MTgzZDU1NWM2ZjNiOTc3ODk2Zjk4NjE5MTEwMjlkYWVmZTA2MWIyMzY3ODE1MWY3Mjk3Y2E2N2M0OWQ1MjIyYjBhZWYxNDYzZTE5ZDU2MGFlYWQyMzZkOTc4YTUwNDRlNTE4NjQwYjExZTA2Zjk1OTNiOTIyNjc4Njk5YWU4MWE3MTM3ODhjYjk0MjdkZDI4ZjA4ZGRjMGIzNmE1Mjk4MDQwMmU5MmU3ZWI0MjllMWZmN2U5NTU3NmJlNmJkNmE4NzJhMDZkYjkzNmNjNGE1MTQ3ZWY1NDRhNmJiNTljMjQ3Mjc2OTcwNTRmMzEzNTc2MmRiNDg5NmZlNjcwNDEwZTA5MjMyMDdmOWNjZmNiZmE3ZWU2NThkNDI3MjI2YWZkZjJlN2RlNzMyYTEyYTg1NGQ4OTg5MWE5OTJmNDE0MWY5Nzg4ZTE2OGQ2YWFkMjc1OGExYjM3NjZkZTVlNmJkZjMyODdlZmQ3MWU4ZTkwMjY3NjMzYmM4YTA3Y2I0ZTYxODRiZDE0MWY1ZmE1MDU0YTE0NWMyZTMxMWQ1ZDNiYWY2MjkwZDIwNTljMDQyN2U2MjAxYWIzYjM5Zjc3ZTA1ZTU0NzVkM2FhYjg5NjcxMjQ2MmZlYTBjNmRjMzJlNDcwZjIxZDliOGZhZmMxMWEzYTUzNGM5NzkzY2ZhZjE2NWI5NTAwNDBhYmE4YmI2MzJkY2I5MzhiYjBhNmNiMDhlYWQ2NmJmMDgxN2E4ODUwZTMwODFlNmQ5NTYzNzJkYzQwOTU3NmQ0ZGY5ZjY4MjM2MTZlMjI5Y2VjNTk5NjQxNjlmOTEyOTBhODUyNjk1MDg2ZGMzYjQ3OTMzOTE1M2Q0MGMzYzM2YTFiZGRjN2IyMTRlM2YzMDY3ODhiOGQwOGVjZWNkMmFlMjk2NzY5MTdlNWU3MjM5NzQyYzlkOTliYTAwZDUyMWQ5NDM2NjRiMGNiYTEyZjBhNTNhMjhjYzg4NGE5NWVhNzdjMSJ9.C_M48hKf9Ccj-lRV6X0co2vwUee5coxYEwoYfSL0ZlM"
}
```
ID Token is a jwt encoded string in this format:
```
{
"sub": "(username)",
"iss": "(issuer URL)",
"name": "(username)",
"aud": "(appid)",
"exp": (expiration time),
"iat": (time of issue),
"auth_time": (time of auth),
"nonce": "(random string)"
}
```
3. Use id_token to contact the /userinfo endpoint, with the "Authorization" HTTP header structured like this:
```
Bearer (id_token)
```
Use access_token to access other scopes, defined in scopes.md
### Public Client (PKCE)
1. PKCE (Proof Key for Code Exchange) is an extension to the OAuth flow which allows for a secure credential exchange without the requirement to provide a client secret. To achieve this, you have to provide a code_verifier for every authorization request. A code_verifier has to be a random string with a minimum length of 43 characters and a maximum length of 128 characters. It can contain alphanumeric characters as well as the characters -, ., _ and ~.
Using this code_verifier string, a new one called code_challenge is created by using one of two methods:
- If you have the required functionality on your client, set code_challenge to be a URL-safe base64-encoded string of the SHA256 hash of code_verifier. In that case, your code_challenge_method becomes S256.
- If you are unable to do so, you can provide your code_verifier as a plain string to code_challenge. Then you have to set your code_challenge_method as plain.
2. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:
```
/login?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&code_challenge_method=CODE_CHALLENGE_METHOD&code_challenge=CODE_CHALLENGE&state=STATE
```
The CLIENT_ID can be obtained by registering an application in the dashboard. The STATE is a random string that will be sent back to your application after the user authorizes. The state parameter is optional, but should be used to prevent CSRF attacks.
The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the REDIRECT_URL, for example:
```
https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE
```
3. Using the provided code from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with application/json and application/x-www-form-urlencoded body, for example:
POST - /api/tokenauth
```
{
"client_id": "YOUR_CLIENT_ID",
"code": "RETURNED_CODE",
"grant_type": "authorization_code",
"redirect_uri": "REDIRECT_URI", (optional, since redirect_uri is not verified on the server)
"code_verifier": "CODE_VERIFIER"
}
```
Response:
```
{
"access_token": "(random string)",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "(random string)",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJleGFtcGxlIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmhlY3RhYml0Lm9yZyIsIm5hbWUiOiJleGFtcGxlIiwiYXVkIjoibXlhcHBpZCIsImV4cCI6MCwiaWF0IjozNjAwLCJhdXRoX3RpbWUiOjAsIm5vbmNlIjoiMDYyMGM2NGNjZmE5MzQ0NTczMTQ5NTM5MmNiMzQzYjM2MGJkMjAwYmIzM2QyNTRjNzQ3YWZjYmY1YzlhNDkzYjM3MWE0MWI2Zjk3YzM5ZWI0ZjU0YmJmNDBjYjNkNjhmNTBmNmFhNjMwMmY2ZjRkM2I0NTI4MWVjYzI2ZTQ5MjNiNDFjYTFhYTIzNjIwOTMzNmU4MGZkZjk1ZGUxMGRiMzNhZWJjY2M3MmM2NjU2ZjI4MmUzZTY0ZTFkZDVlMzkxOGJlMmM2MDA4ZmFlZGZiNGVjOGE3NDk5Y2JkMzI1NGNjM2Q0ZGI0ZTMyNjQ4NDgxZDY2Mjg0YmNkMjgyZDVkNGQ3NDg2NjU2NWZjNjhlYTM0ZTIxYmE3MTgzZDU1NWM2ZjNiOTc3ODk2Zjk4NjE5MTEwMjlkYWVmZTA2MWIyMzY3ODE1MWY3Mjk3Y2E2N2M0OWQ1MjIyYjBhZWYxNDYzZTE5ZDU2MGFlYWQyMzZkOTc4YTUwNDRlNTE4NjQwYjExZTA2Zjk1OTNiOTIyNjc4Njk5YWU4MWE3MTM3ODhjYjk0MjdkZDI4ZjA4ZGRjMGIzNmE1Mjk4MDQwMmU5MmU3ZWI0MjllMWZmN2U5NTU3NmJlNmJkNmE4NzJhMDZkYjkzNmNjNGE1MTQ3ZWY1NDRhNmJiNTljMjQ3Mjc2OTcwNTRmMzEzNTc2MmRiNDg5NmZlNjcwNDEwZTA5MjMyMDdmOWNjZmNiZmE3ZWU2NThkNDI3MjI2YWZkZjJlN2RlNzMyYTEyYTg1NGQ4OTg5MWE5OTJmNDE0MWY5Nzg4ZTE2OGQ2YWFkMjc1OGExYjM3NjZkZTVlNmJkZjMyODdlZmQ3MWU4ZTkwMjY3NjMzYmM4YTA3Y2I0ZTYxODRiZDE0MWY1ZmE1MDU0YTE0NWMyZTMxMWQ1ZDNiYWY2MjkwZDIwNTljMDQyN2U2MjAxYWIzYjM5Zjc3ZTA1ZTU0NzVkM2FhYjg5NjcxMjQ2MmZlYTBjNmRjMzJlNDcwZjIxZDliOGZhZmMxMWEzYTUzNGM5NzkzY2ZhZjE2NWI5NTAwNDBhYmE4YmI2MzJkY2I5MzhiYjBhNmNiMDhlYWQ2NmJmMDgxN2E4ODUwZTMwODFlNmQ5NTYzNzJkYzQwOTU3NmQ0ZGY5ZjY4MjM2MTZlMjI5Y2VjNTk5NjQxNjlmOTEyOTBhODUyNjk1MDg2ZGMzYjQ3OTMzOTE1M2Q0MGMzYzM2YTFiZGRjN2IyMTRlM2YzMDY3ODhiOGQwOGVjZWNkMmFlMjk2NzY5MTdlNWU3MjM5NzQyYzlkOTliYTAwZDUyMWQ5NDM2NjRiMGNiYTEyZjBhNTNhMjhjYzg4NGE5NWVhNzdjMSJ9.C_M48hKf9Ccj-lRV6X0co2vwUee5coxYEwoYfSL0ZlM"
}
```
3. Use id_token to contact the /userinfo endpoint, with the "Authorization" HTTP header structured like this:
```
Bearer (id_token)
```
Use access_token to access other scopes, defined in scopes.md

598
main.save Normal file
View File

@ -0,0 +1,598 @@
#!/usr/bin/python3
import hashlib
import base64
import jwt
import os
import sqlite3
import time
import secrets
import configparser
import asyncio
from hypercorn.config import Config
from hypercorn.asyncio import serve
from werkzeug.security import generate_password_hash, check_password_hash
from quart import Quart, render_template, request, url_for, flash, redirect, session, make_response, send_from_directory, stream_with_context, Response, request
# Parse configuration file, and check if anything is wrong with it
if not os.path.exists("config.ini"):
print("config.ini does not exist")
quit(1)
config = configparser.ConfigParser()
config.read("config.ini")
HOST = config["config"]["HOST"]
PORT = config["config"]["PORT"]
SECRET_KEY = config["config"]["SECRET_KEY"]
MAX_STORAGE = config["config"]["MAX_STORAGE"]
if SECRET_KEY == "supersecretkey" or SECRET_KEY == "placeholder":
print("[WARNING] Secret key not set")
# Define Quart
app = Quart(__name__)
app.config["SECRET_KEY"] = SECRET_KEY
# Hash creation function
def sha256_base64(s: str) -> str:
hashed = hashlib.sha256(s.encode()).digest()
encoded = base64.urlsafe_b64encode(hashed).rstrip(b'=').decode()
return encoded
# Database functions
def get_db_connection():
conn = sqlite3.connect("database.db")
conn.row_factory = sqlite3.Row
return conn
def get_user(id):
conn = get_db_connection()
post = conn.execute("SELECT * FROM users WHERE id = ?",
(id,)).fetchone()
conn.close()
if post is None:
return None
return post
def get_session(id):
conn = get_db_connection()
post = conn.execute("SELECT * FROM sessions WHERE session = ?",
(id,)).fetchone()
conn.close()
if post is None:
return None
return post
def get_session_from_sessionid(id):
conn = get_db_connection()
post = conn.execute("SELECT * FROM sessions WHERE sessionid = ?",
(id,)).fetchone()
conn.close()
if post is None:
return None
return post
def check_username_taken(username):
conn = get_db_connection()
post = conn.execute("SELECT * FROM users WHERE lower(username) = ?",
(username.lower(),)).fetchone()
conn.close()
if post is None:
return None
return post["id"]
async def oauth2_token_refresh(openid, appId):
while True:
await asyncio.sleep(3600)
conn = get_db_connection()
# Fetch required data in a single query
login_data = conn.execute("SELECT nextcode, nextsecret, nextopenid, creator FROM logins WHERE appId = ? AND openid = ?", (str(appId), str(openid))).fetchone()
user = get_user(int(login_data[3]))
datatemplate = {
"sub": user["username"],
"iss": "https://auth.hectabit.org",
"name": user["username"],
"aud": appId,
"exp": time.time() + 3600,
"iat": time.time(),
"auth_time": time.time(),
"nonce": str(secrets.token_hex(512))
}
jwt_token = jwt.encode(datatemplate, SECRET_KEY, algorithm='HS256')
if login_data:
nextcode = login_data[0]
nextsecret = login_data[1]
nextopenid = login_data[2]
conn.execute("UPDATE logins SET code = ?, nextcode = ?, secret = ?, nextsecret = ?, openid = ?, nextopenid = ? WHERE appId = ? AND openid = ?", (nextcode, str(secrets.token_hex(512)), nextsecret, str(secrets.token_hex(512)), nextopenid, str(jwt_token), str(appId), str(openid)))
conn.commit()
conn.close()
else:
conn.close()
return
# Disable CORS
@app.after_request
async def add_cors_headers(response):
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "*")
response.headers.add("Access-Control-Allow-Methods", "*")
return response
@app.route("/api/version", methods=("GET", "POST"))
async def apiversion():
return "Burgerauth Version 1.2"
@app.route("/api/signup", methods=("GET", "POST"))
async def apisignup():
if request.method == "POST":
data = await request.get_json()
username = data["username"]
password = data["password"]
if username == "":
return {}, 422
if len(username) > 20:
return {}, 422
if not username.isalnum():
return {}, 422
if password == "":
return {}, 422
if len(password) < 14:
return {}, 422
if not check_username_taken(username) == None:
return {}, 409
hashedpassword = generate_password_hash(password)
conn = get_db_connection()
conn.execute("INSERT INTO users (username, password, created) VALUES (?, ?, ?)",
(username, hashedpassword, str(time.time())))
conn.commit()
conn.close()
userID = check_username_taken(username)
user = get_user(userID)
randomCharacters = secrets.token_hex(512)
conn = get_db_connection()
conn.execute("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)",
(randomCharacters, userID, request.headers.get("user-agent")))
conn.commit()
conn.close()
return {
"key": randomCharacters
}, 200
@app.route("/api/login", methods=("GET", "POST"))
async def apilogin():
if request.method == "POST":
data = await request.get_json()
username = data["username"]
password = data["password"]
passwordchange = data["passwordchange"]
newpass = data["newpass"]
check_username_thing = check_username_taken(username)
if check_username_thing == None:
return {}, 401
userID = check_username_taken(username)
user = get_user(userID)
if not check_password_hash(user["password"], (password)):
return {}, 401
randomCharacters = secrets.token_hex(512)
conn = get_db_connection()
conn.execute("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)",
(randomCharacters, userID, request.headers.get("user-agent")))
conn.commit()
conn.close()
if passwordchange == "yes":
hashedpassword = generate_password_hash(newpass)
conn = get_db_connection()
conn.execute("UPDATE users SET password = ? WHERE username = ?", (hashedpassword, username))
conn.commit()
conn.close()
return {
"key": randomCharacters,
}, 200
@app.route("/api/userinfo", methods=("GET", "POST"))
async def apiuserinfo():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
datatemplate = {
"username": user["username"],
"id": user["id"],
"created": user["created"]
}
return datatemplate
@app.route("/userinfo", methods=("GET", "POST"))
async def apiopeniduserinfo():
if request.method == "GET":
access_token = request.headers.get('Authorization').split(' ')[1]
conn = get_db_connection()
userid = int(conn.execute("SELECT creator FROM logins WHERE code = ?", (str(access_token),)).fetchone()[0])
user = get_user(userid)
conn.close()
datatemplate = {
"sub": user["username"],
"name": user["username"]
}
return datatemplate
@app.route("/api/auth", methods=("GET", "POST"))
async def apiauthenticate():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
appId = data["appId"]
code = data["code"]
codemethod = data["codemethod"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection()
secretkey = str(secrets.token_hex(512))
appidcheck = str(conn.execute("SELECT appId FROM oauth WHERE appId = ?", (str(appId),)).fetchone()[0])
if not str(appidcheck) == str(appId):
return {}, 401
datatemplate = {
"sub": user["username"],
"iss": "https://auth.hectabit.org",
"name": user["username"],
"aud": appId,
"exp": time.time() + 3600,
"iat": time.time(),
"auth_time": time.time(),
"nonce": str(secrets.token_hex(512))
}
jwt_token = jwt.encode(datatemplate, SECRET_KEY, algorithm='HS256')
datatemplate2 = {
"sub": user["username"],
"iss": "https://auth.hectabit.org",
"name": user["username"],
"aud": appId,
"exp": time.time() + 7200,
"iat": time.time() + 3600,
"auth_time": time.time(),
"nonce": str(secrets.token_hex(512))
}
nextjwt_token = jwt.encode(datatemplate2, SECRET_KEY, algorithm='HS256')
conn.execute("INSERT INTO logins (appId, secret, nextsecret, code, nextcode, creator, openid, nextopenid, pkce, pkcemethod) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(str(appId), str(secretkey), str(secrets.token_hex(512)), str(secrets.token_hex(512)), str(secrets.token_hex(512)), int(user["id"]), str(jwt_token), str(nextjwt_token), str(code), str(codemethod)))
conn.commit()
conn.close()
if secretkey:
return secretkey, 200
else:
return {}, 400
@app.route("/api/tokenauth", methods=("GET", "POST"))
async def apitokenexchange():
if request.method == "POST":
data = await request.form
appId = data["client_id"]
code = data["code"]
if "code_verifier" in data:
code_verify = data["code_verifier"]
verifycode = True
else:
secret = data["client_secret"]
verifycode = False
conn = get_db_connection()
# Fetch required data in a single query
oauth_data = conn.execute("SELECT appId, secret FROM oauth WHERE appId = ?", (str(appId),)).fetchone()
if not oauth_data or oauth_data["appId"] != appId:
return {}, 401
login_data = conn.execute("SELECT openid, code, pkce, pkcemethod FROM logins WHERE appId = ? AND secret = ?", (str(appId), str(code))).fetchone()
if verifycode:
if str(login_data["pkce"]) == "none":
return {}, 400
else:
if str(login_data["pkcemethod"]) == "S256":
if str(sha256_base64(code_verify)) != str(login_data["pkce"]):
return {}, 403
elif str(login_data["pkcemethod"]) == "plain":
if str(code_verify) != str(login_data["pkce"]):
return {}, 403
else:
return {}, 501
else:
if not oauth_data["secret"] == secret:
return {}, 401
newkey = str(secrets.token_hex(512))
conn.execute("UPDATE logins SET secret = ?, nextsecret = ? WHERE appId = ? AND secret = ?", (str(newkey), str(secrets.token_hex(512)), str(appId), str(code)))
conn.close()
if login_data:
access_token = {
"access_token": str(login_data["code"]),
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": newkey,
"id_token": str(login_data["openid"])
}
asyncio.create_task(oauth2_token_refresh(login_data["openid"], appId))
return access_token, 200
else:
return {}, 400
@app.route("/api/deleteauth", methods=("GET", "POST"))
async def apideleteauth():
if request.method == "POST":
data = await request.get_json()
appId = data["appId"]
secretKey = data["secretKey"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection()
try:
conn.execute("DELETE FROM oauth WHERE appId = ? AND creator = ?", (str(appId), int(user["id"])))
conn.commit()
conn.close()
except:
return {}, 400
else:
return {}, 200
@app.route("/api/newauth", methods=("GET", "POST"))
async def apicreateauth():
if request.method == "POST":
data = await request.get_json()
appId = data["appId"]
secretKey = data["secretKey"]
secret = str(secrets.token_hex(512))
conn = get_db_connection()
while True:
try:
conn.execute("SELECT secret FROM oauth WHERE secret = ?", (str(secret),)).fetchone()[0]
except:
break
else:
secret = str(secrets.token_hex(512))
continue
try:
conn.execute("SELECT secret FROM oauth WHERE appId = ?", (str(appId),)).fetchone()[0]
except:
print("New Oauth added with ID", appId)
else:
return {}, 401
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn.execute("INSERT INTO oauth (appId, creator, secret) VALUES (?, ?, ?)",
(str(appId),int(user["id"]),str(secret)))
conn.commit()
conn.close()
secretkey = {
"key": secret
}
return secretkey, 200
@app.route("/api/listauth", methods=("GET", "POST"))
async def apiauthlist():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection()
oauths = conn.execute("SELECT * FROM oauth WHERE creator = ? ORDER BY creator DESC;", (user["id"],)).fetchall()
conn.close()
datatemplate = []
for i in oauths:
template = {
"appId": i["appId"]
}
datatemplate.append(template)
return datatemplate, 200
@app.route("/api/deleteaccount", methods=("GET", "POST"))
async def apideleteaccount():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection()
try:
conn.execute("DELETE FROM userdata WHERE creator = ?", (userCookie["id"],))
except:
pass
else:
pass
try:
conn.execute("DELETE FROM logins WHERE creator = ?", (userCookie["id"],))
except:
pass
else:
pass
try:
conn.execute("DELETE FROM oauth WHERE creator = ?", (userCookie["id"],))
except:
pass
else:
pass
try:
conn.execute("DELETE FROM users WHERE id = ?", (userCookie["id"],))
except:
return {}, 400
else:
pass
conn.commit()
conn.close()
return {}, 200
@app.route("/api/sessions/list", methods=("GET", "POST"))
async def apisessionslist():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection()
sessions = conn.execute("SELECT * FROM sessions WHERE id = ? ORDER BY id DESC;", (user["id"],)).fetchall()
conn.close()
datatemplate = []
for x in sessions:
device = x["device"]
thisSession = False
if (x["session"] == secretKey):
thisSession = True
sessiontemplate = {
"id": x["sessionid"],
"thisSession": thisSession,
"device": device
}
datatemplate.append(sessiontemplate)
return datatemplate, 200
@app.route("/api/sessions/remove", methods=("GET", "POST"))
async def apisessionsremove():
if request.method == "POST":
data = await request.get_json()
secretKey = data["secretKey"]
sessionId = data["sessionId"]
userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
session = get_session_from_sessionid(sessionId)
if (session != None):
if (user["id"] == session["id"]):
conn = get_db_connection()
conn.execute("DELETE FROM sessions WHERE sessionid = ?", (session["sessionid"],))
conn.commit()
conn.close()
return {}, 200
else:
return {}, 403
else:
return {}, 422
@app.route("/listusers/<secretkey>", methods=("GET", "POST"))
def listusers(secretkey):
if secretkey == SECRET_KEY:
conn = get_db_connection()
users = conn.execute("SELECT * FROM users").fetchall()
conn.close()
thing = ""
for x in users:
thing = str(x["id"]) + " - " + x["username"] + " - " + str(get_space(x["id"])) + "<br>" + thing
return thing
else:
return redirect("/")
@app.errorhandler(500)
async def burger(e):
return {}, 500
@app.errorhandler(404)
async def burger(e):
return {}, 404
@app.route("/")
async def index():
return redirect("/login", code=302)
@app.route("/login")
async def login():
return await render_template("login.html")
@app.route("/signup")
async def signup():
return await render_template("signup.html")
@app.route("/logout")
async def logout():
return await render_template("logout.html")
@app.route("/app")
async def mainapp():
return await render_template("main.html")
@app.route("/dashboard")
async def dashboard():
return await render_template("dashboard.html")
@app.route("/.well-known/openid-configuration")
async def openid():
return await render_template("openid.json")
# Start server
hypercornconfig = Config()
hypercornconfig.bind = (HOST + ":" + PORT)
if __name__ == "__main__":
print("[INFO] Server started")
asyncio.run(serve(app, hypercornconfig))
print("[INFO] Server stopped")

39
scopes.md Normal file
View File

@ -0,0 +1,39 @@
# Scopes
Burgerauth comes with predefined scopes used for syncing data across applications.
However, Burgerauth assumes all clients request all scopes in OAuth2 mode, as to keep compatibility with the base Burgerauth system.
## Endpoints
### RDIR - /rsakeyshare
This one requires more explanation. This is used for clients who wish to have a RSA key unique to the account. To get this RSA Key, create a redirect like this in JS:
```
window.postMessage("JSON.stringify({"access_token": "ACCESS_TOKEN", "redirect_uri": "REDIRECT_URI"})", https://auth.hectabit.org/rsakeyshare);
```
REDIRECT_URI is the correspondding URI where a Burgerauth LocalStorage acceptor is set up. Here is some JS code for a basic acceptor:
```
window.addEventListener(
"message",
(event) => {
if (event.origin !== "https://auth.hectabit.org/rsakeyshare") return;
localStorage.setItem("DONOTSHARE-rsakey", message)
},
false,
);
```
Please use this on client-side only, to maintain security and E2EE best practices.
### POST - /api/uniqueid
Provide "access_token"
Returns a ID unique to the user in this format:
```
{
"uniqueid": "(random string)"
}
```