Compare commits
16 commits
main
...
test_oauth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d87d9cb90 | ||
|
|
f206006e65 | ||
|
|
a9c2356f79 | ||
|
|
e081fdc446 | ||
|
|
09daaa2666 | ||
|
|
4f20d8dde0 | ||
|
|
07ee332b4c | ||
|
|
2b76b00e9a | ||
|
|
a6c811847a | ||
|
|
eab18dd9b4 | ||
|
|
caf5d76050 | ||
|
|
25baa6825e | ||
|
|
a6c4847ae2 | ||
|
|
a5fe0e246f | ||
|
|
771dd8bc49 | ||
|
|
a78c97bb09 |
5 changed files with 176 additions and 1 deletions
|
|
@ -176,6 +176,11 @@ def is_alphabet_space_dash(word):
|
||||||
return r.match(word) is not None
|
return r.match(word) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def remove_non_alphabet_space_dash(word):
|
||||||
|
r = re.compile("[^a-zA-Z -]")
|
||||||
|
return r.sub('', word)
|
||||||
|
|
||||||
|
|
||||||
def merge_lists_by_key(l1, l2, key):
|
def merge_lists_by_key(l1, l2, key):
|
||||||
merged = {}
|
merged = {}
|
||||||
for item in l1 + l2:
|
for item in l1 + l2:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
|
||||||
import schemas
|
import schemas
|
||||||
from chalicelib.core import users, telemetry, tenants
|
from chalicelib.core import users, telemetry, tenants, authorizers
|
||||||
from chalicelib.utils import captcha, smtp
|
from chalicelib.utils import captcha, smtp
|
||||||
from chalicelib.utils import helper
|
from chalicelib.utils import helper
|
||||||
from chalicelib.utils import pg_client
|
from chalicelib.utils import pg_client
|
||||||
|
|
@ -98,3 +98,88 @@ async def create_tenant(data: schemas.UserSignupSchema):
|
||||||
"user": r
|
"user": r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def __plan_generator(type_name, trial_period, users_limit, data_retention, projects_limit, sessions_limits):
|
||||||
|
return {
|
||||||
|
"type": type_name,
|
||||||
|
"trial": trial_period,
|
||||||
|
"users": users_limit,
|
||||||
|
"dataRetention": data_retention,
|
||||||
|
"projects": projects_limit,
|
||||||
|
"sessions": sessions_limits,
|
||||||
|
# The following params can be overwritten in each plan change
|
||||||
|
"stripeCustomerId": None,
|
||||||
|
"stripeSubscriptionId": None,
|
||||||
|
"endAt": None,
|
||||||
|
"billingStartsAt": None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FREE_PLAN = __plan_generator(type_name="free",
|
||||||
|
trial_period=0,
|
||||||
|
users_limit=2,
|
||||||
|
data_retention=7,
|
||||||
|
projects_limit=1,
|
||||||
|
sessions_limits=1000)
|
||||||
|
|
||||||
|
|
||||||
|
async def create_oauth_tenant(fullname: str, email: str):
|
||||||
|
logger.info(f"==== Signup oauth started at {TimeUTC.to_human_readable(TimeUTC.now())} UTC")
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
logger.debug(f"email: {email}")
|
||||||
|
|
||||||
|
if email is None or len(email) < 5:
|
||||||
|
errors.append("Invalid email address.")
|
||||||
|
else:
|
||||||
|
if users.email_exists(email):
|
||||||
|
users.update_user_internal_id(email, email)
|
||||||
|
return users.authenticate_sso(email, email)
|
||||||
|
if users.get_deleted_user_by_email(email) is not None:
|
||||||
|
errors.append("Email address previously deleted.")
|
||||||
|
|
||||||
|
if fullname is None or len(fullname) < 1 or not helper.is_alphabet_space_dash(fullname):
|
||||||
|
edited_fullname = helper.remove_non_alphabet_space_dash(fullname)
|
||||||
|
if len(edited_fullname) < 1:
|
||||||
|
errors.append("Invalid full name.")
|
||||||
|
fullname = edited_fullname
|
||||||
|
|
||||||
|
if len(errors) > 0:
|
||||||
|
logger.warning(
|
||||||
|
f"==> signup error for:\n email:{email}, fullname:{fullname}")
|
||||||
|
logger.warning(errors)
|
||||||
|
return {"errors": errors}
|
||||||
|
|
||||||
|
project_name = "my first project"
|
||||||
|
params = {
|
||||||
|
"email": email, "fullname": fullname, "projectName": project_name,
|
||||||
|
"data": json.dumps({"lastAnnouncementView": TimeUTC.now()}),
|
||||||
|
"permissions": [p.value for p in schemas.Permissions],
|
||||||
|
"plan": json.dumps(FREE_PLAN)
|
||||||
|
}
|
||||||
|
query = """WITH t AS (
|
||||||
|
INSERT INTO public.tenants (name, plan)
|
||||||
|
VALUES ('my organisation', %(plan)s::jsonb)
|
||||||
|
RETURNING tenant_id, api_key
|
||||||
|
),
|
||||||
|
r AS (
|
||||||
|
INSERT INTO public.roles(tenant_id, name, description, permissions, protected)
|
||||||
|
VALUES ((SELECT tenant_id FROM t), 'Owner', 'Owner', %(permissions)s::text[], TRUE),
|
||||||
|
((SELECT tenant_id FROM t), 'Member', 'Member', %(permissions)s::text[], FALSE)
|
||||||
|
RETURNING *
|
||||||
|
),
|
||||||
|
u AS (
|
||||||
|
INSERT INTO public.users (tenant_id, email, role, name, verified_email, data, role_id, origin, internal_id)
|
||||||
|
VALUES ((SELECT tenant_id FROM t), %(email)s, 'owner', %(fullname)s, TRUE,%(data)s, (SELECT role_id FROM r WHERE name ='Owner'), 'google', %(email)s)
|
||||||
|
RETURNING user_id,email,role,name,role_id
|
||||||
|
)
|
||||||
|
INSERT INTO public.projects (tenant_id, name, active)
|
||||||
|
VALUES ((SELECT t.tenant_id FROM t), %(projectName)s, TRUE)
|
||||||
|
RETURNING tenant_id,project_id, (SELECT api_key FROM t) AS api_key;"""
|
||||||
|
|
||||||
|
with pg_client.PostgresClient() as cur:
|
||||||
|
cur.execute(cur.mogrify(query, params))
|
||||||
|
r = cur.fetchone()
|
||||||
|
|
||||||
|
return users.authenticate_sso(email, email)
|
||||||
|
|
|
||||||
|
|
@ -1020,3 +1020,15 @@ def update_user_settings(user_id, settings):
|
||||||
{"user_id": user_id, "settings": json.dumps(settings)})
|
{"user_id": user_id, "settings": json.dumps(settings)})
|
||||||
)
|
)
|
||||||
return helper.dict_to_camel_case(cur.fetchone())
|
return helper.dict_to_camel_case(cur.fetchone())
|
||||||
|
|
||||||
|
|
||||||
|
def update_user_internal_id(email, internal_id):
|
||||||
|
with pg_client.PostgresClient() as cur:
|
||||||
|
cur.execute(
|
||||||
|
cur.mogrify(
|
||||||
|
f"""UPDATE public.users
|
||||||
|
SET internal_id = %(internal_id)s
|
||||||
|
WHERE email = %(email)s
|
||||||
|
AND deleted_at IS NULL;""",
|
||||||
|
{"email": email, "internal_id": internal_id})
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,4 @@ python3-saml==1.16.0 --no-binary=lxml
|
||||||
redis==5.1.0b6
|
redis==5.1.0b6
|
||||||
#confluent-kafka==2.1.0
|
#confluent-kafka==2.1.0
|
||||||
azure-storage-blob==12.22.0
|
azure-storage-blob==12.22.0
|
||||||
|
httpx==0.23.0
|
||||||
|
|
@ -5,6 +5,8 @@ from decouple import config
|
||||||
from fastapi import Body, Depends, BackgroundTasks, Request
|
from fastapi import Body, Depends, BackgroundTasks, Request
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from starlette.responses import RedirectResponse, FileResponse, JSONResponse, Response
|
from starlette.responses import RedirectResponse, FileResponse, JSONResponse, Response
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
|
||||||
import schemas
|
import schemas
|
||||||
from chalicelib.core import scope
|
from chalicelib.core import scope
|
||||||
|
|
@ -55,6 +57,76 @@ if config("MULTI_TENANTS", cast=bool, default=False) or not tenants.tenants_exis
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
# Environment variables (ensure these are set)
|
||||||
|
GOOGLE_CLIENT_ID = config("GOOGLE_CLIENT_ID")
|
||||||
|
GOOGLE_CLIENT_SECRET = config("GOOGLE_CLIENT_SECRET")
|
||||||
|
GOOGLE_REDIRECT_URI = config("GOOGLE_REDIRECT_URI")
|
||||||
|
|
||||||
|
|
||||||
|
if True or config("MULTI_TENANTS", cast=bool, default=False) or not tenants.tenants_exists_sync(use_pool=False):
|
||||||
|
@public_app.get('/signup-oauth', tags=['signup'])
|
||||||
|
async def signup_oauth_handler():
|
||||||
|
google_authorization_url = (
|
||||||
|
"https://accounts.google.com/o/oauth2/auth"
|
||||||
|
f"?client_id={GOOGLE_CLIENT_ID}"
|
||||||
|
f"&response_type=code"
|
||||||
|
f"&redirect_uri={GOOGLE_REDIRECT_URI}"
|
||||||
|
f"&scope=email%20profile"
|
||||||
|
)
|
||||||
|
return RedirectResponse(url=google_authorization_url)
|
||||||
|
|
||||||
|
|
||||||
|
if True or config("MULTI_TENANTS", cast=bool, default=False) or not tenants.tenants_exists_sync(use_pool=False):
|
||||||
|
@public_app.get('/signup-oauth-callback', tags=['signup'])
|
||||||
|
async def signup_oauth_callback_handler(code: str):
|
||||||
|
# Exchange code for token
|
||||||
|
token_response = httpx.post(
|
||||||
|
url="https://oauth2.googleapis.com/token",
|
||||||
|
data={
|
||||||
|
"client_id": GOOGLE_CLIENT_ID,
|
||||||
|
"client_secret": GOOGLE_CLIENT_SECRET,
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"redirect_uri": GOOGLE_REDIRECT_URI,
|
||||||
|
},
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
)
|
||||||
|
token_response_data = token_response.json()
|
||||||
|
access_token = token_response_data.get("access_token")
|
||||||
|
|
||||||
|
if not access_token:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not retrieve access token")
|
||||||
|
|
||||||
|
# Retrieve user info
|
||||||
|
user_info_response = httpx.get(
|
||||||
|
url="https://www.googleapis.com/oauth2/v1/userinfo",
|
||||||
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
|
)
|
||||||
|
user_info = user_info_response.json()
|
||||||
|
name = user_info.get("name")
|
||||||
|
email = user_info.get("email")
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Could not retrieve user email")
|
||||||
|
if not name:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Could not retrieve user name")
|
||||||
|
|
||||||
|
content = await signup.create_oauth_tenant(name, email)
|
||||||
|
if content is None:
|
||||||
|
return {"errors": ["null JWT"]}
|
||||||
|
if "errors" in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
refresh_token = content.pop("refreshToken")
|
||||||
|
refresh_token_max_age = content.pop("refreshTokenMaxAge")
|
||||||
|
response = Response(
|
||||||
|
status_code=status.HTTP_302_FOUND,
|
||||||
|
headers={'Location': config("SITE_URL") + "/login?jwt=" + content.pop("jwt")})
|
||||||
|
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/refresh",
|
||||||
|
max_age=refresh_token_max_age, secure=True, httponly=True)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@public_app.post('/login', tags=["authentication"])
|
@public_app.post('/login', tags=["authentication"])
|
||||||
def login_user(response: JSONResponse, spot: Optional[bool] = False, data: schemas.UserLoginSchema = Body(...)):
|
def login_user(response: JSONResponse, spot: Optional[bool] = False, data: schemas.UserLoginSchema = Body(...)):
|
||||||
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
|
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue