Skip to content

Commit

Permalink
Golden-Monkey-15 (#699)
Browse files Browse the repository at this point in the history
* Update devapp1.yml

Next Milestone

* dark mode toggler and filter invert (#709)

* Issue 693 Create RTMP media in Studio (#711)

* shared configs and qr code for stream preview

* auto generate unique key and check for duplication

* footer version

Co-authored-by: Hồng Phát <phatnh4@vng.com.vn>

* remove unnecessary roles (#718)

* UpStage Studio V1.4 (#721)

* voice setting form

* save and load voice

* replicate voices

* turn off media section and fix stream voice

* optimize

* multiframe preview

* no voice in stage

* fix typo and footer version (#724)

Co-authored-by: Hồng Phát <phatnh4@vng.com.vn>

* navigations between pages (#726)

* Jitsi experiments - Meeting tool (#727)

* meeting and room

* jitsi iframe api

* foyer link

* fix firefox meeting issue and free resize (#728)

* Issue 731 Forgot password (#732)

* password reset in 3 steps

* email template

* Issue 733 Email notifications (#734)

* rich text editor with tiny mce and send emails

* rich text editor for foyer description

* success messages

* no agreed to terms (#736)

* fix tiny repo in reset password email

Co-authored-by: Hồng Phát <hongphat.js@gmail.com>
Co-authored-by: Hồng Phát <phatnh4@vng.com.vn>
  • Loading branch information
3 people authored Jan 9, 2022
1 parent 1380011 commit a5ba7c1
Show file tree
Hide file tree
Showing 146 changed files with 24,969 additions and 624 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/devapp1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: DEVAPP1 CI
on:
# Triggers the workflow on push or approved pull request on R1-2021 branch
push:
branches: [ TC-14 ]
branches: [ Golden-Monkey-15 ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -32,7 +32,7 @@ jobs:
script: |
cd /home/upstage/upstage/ui/dashboard/
git fetch
git checkout TC-14
git checkout Golden-Monkey-15
git pull
yarn
yarn build:dev
Expand Down
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,7 @@ VUE_APP_STREAMING_PASSWORD=admin

3. `Studio` configurations

Studio uses a smaller set of configurations compared to `Dashboard`. There is a small different: configuration name in Studio must be started with `VITE_APP`:

```yml
VITE_APP_GRAPHQL_ENDPOINT=https://upstage.live/V4.0/studio_graphql/
VITE_APP_STATIC_ASSETS_ENDPOINT=https://upstage.live/V4.0/static/assets/
```
Studio and dashboard share the same access token and configurations base. In other word, you don't have to set up separate environment for studio.

4. `Nginx` configurations

Expand Down
156 changes: 138 additions & 18 deletions auth/auth_mutation.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
import sys,os
from config.project_globals import app, DBSession, ScopedSession
from auth.fernet_crypto import encrypt, decrypt
from flask_jwt_extended import (jwt_required, get_jwt_identity,
create_access_token, create_refresh_token,
verify_jwt_in_request, JWTManager)
from user.models import OneTimeTOTP, User
from auth.models import UserSession
from auth.auth_api import TNL
from flask import request
import sys
import os
import graphene
import pyotp

from mail.mail_utils import send
appdir = os.path.abspath(os.path.dirname(__file__))
projdir = os.path.abspath(os.path.join(appdir,'..'))
projdir = os.path.abspath(os.path.join(appdir, '..'))
if projdir not in sys.path:
sys.path.append(appdir)
sys.path.append(projdir)

from flask import request
from flask_jwt_extended import (jwt_required,get_jwt_identity,
create_access_token,create_refresh_token,
verify_jwt_in_request,JWTManager)

from config.project_globals import app,DBSession,ScopedSession
from auth.fernet_crypto import encrypt,decrypt
from auth.auth_api import TNL
from auth.models import UserSession
from user.models import User

jwt = JWTManager(app)


class AuthMutation(graphene.Mutation):
access_token = graphene.String()
refresh_token = graphene.String()

class Arguments:
username = graphene.String()
password = graphene.String()
def mutate(self, info , username, password) :

def mutate(self, info, username, password):
user = User.query.filter_by(username=username).first()
if not user:
raise Exception('Authenication Failure : User is not registered')
if decrypt(password) != user.password:
raise Exception('Authenication Failure : Bad Password')
return AuthMutation(
access_token = create_access_token(username),
refresh_token = create_refresh_token(username)
access_token=create_access_token(username),
refresh_token=create_refresh_token(username)
)
# TODO: Add session handling, etc.


class RefreshMutation(graphene.Mutation):
class Arguments(object):
refresh_token = graphene.String()
Expand All @@ -51,8 +55,8 @@ def mutate(self, info, refresh_token):
current_user_id = get_jwt_identity()
refresh_token = request.headers[app.config['JWT_HEADER_NAME']]

user = DBSession.query(User).filter(User.id==current_user_id
).filter(User.active==True).first()
user = DBSession.query(User).filter(User.id == current_user_id
).filter(User.active == True).first()

if not user:
TNL.add(refresh_token)
Expand All @@ -76,3 +80,119 @@ def mutate(self, info, refresh_token):
user_session_id = user_session.id

return RefreshMutation(new_token=access_token)


class RequestPasswordResetMutation(graphene.Mutation):
class Arguments(object):
username_or_email = graphene.String()

message = graphene.String()
username = graphene.String()

def mutate(self, info, username_or_email):
with ScopedSession() as local_db_session:
if '@' in username_or_email:
user = local_db_session.query(User).filter(
User.email == username_or_email).first()
else:
user = local_db_session.query(User).filter(
User.username == username_or_email).first()

if user:
email = user.email
username = user.username
else:
raise Exception(
f"We cannot find any user with this {'email' if '@' in username_or_email else 'username'}!")

totp = pyotp.TOTP(pyotp.random_base32())
otp = totp.now()

local_db_session.query(OneTimeTOTP).filter(
OneTimeTOTP.user_id == user.id).delete()
local_db_session.flush()
local_db_session.add(OneTimeTOTP(user_id=user.id, code=otp))
local_db_session.flush()
send(email, f"Password reset for account {user.username}",
f"""
<p>
Hi <b>{user.display_name if user.display_name else user.username}</b>,
<br>
<br>
We received a request to reset your forgotten password. Please use the following code to proceed your password reset:
<b style="color: #007011">{otp}</b>
<br>
The code will expire in 30 minutes.
<br>
<br>
If you did not request a password reset, please ignore this email.
<br>
<br>
Thank you,
<br>
<i style="color: #007011">The Upstage Team!</i>
</p>
""")

return RequestPasswordResetMutation(
message=f"We've sent an email with a code to reset your password to {email}.",
username=username
)


class VerifyPasswordResetMutation(graphene.Mutation):
class Arguments(object):
username = graphene.String()
otp = graphene.String()

message = graphene.String()

def mutate(self, info, username, otp):
with ScopedSession() as local_db_session:
user = local_db_session.query(User).filter(
User.username == username).first()

if not user:
raise Exception(
f"We cannot find any user with this username!")

otp_record = local_db_session.query(OneTimeTOTP).filter(
OneTimeTOTP.user_id == user.id).first()

if not otp_record or otp_record.code != otp:
raise Exception(
f"Invalid OTP code. Please try again.")

return VerifyPasswordResetMutation(message=f"Valid OTP code. Please enter a new password.")


class PasswordResetMutation(graphene.Mutation):
class Arguments(object):
username = graphene.String()
otp = graphene.String()
password = graphene.String()

message = graphene.String()

def mutate(self, info, username, otp, password):
with ScopedSession() as local_db_session:
user = local_db_session.query(User).filter(
User.username == username).first()

if not user:
raise Exception(
f"We cannot find any user with this username!")

otp_record = local_db_session.query(OneTimeTOTP).filter(
OneTimeTOTP.user_id == user.id).first()

if not otp_record or otp_record.code != otp:
raise Exception(
f"Invalid OTP code. Please try again.")

local_db_session.query(OneTimeTOTP).filter(
OneTimeTOTP.user_id == user.id).delete()
user.password = encrypt(password)
local_db_session.flush()

return PasswordResetMutation(message=f"Password reset successfully.")
29 changes: 29 additions & 0 deletions config/schema.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# -*- coding: iso8859-15 -*-
from os import name

from flask_jwt_extended.view_decorators import jwt_required
from config.project_globals import DBSession, ScopedSession, app
from flask_graphql import GraphQLView
from config.settings import NGINX_CONFIG_FILE, VERSION
from graphene import relay
import graphene
from config.models import Config as ConfigModel
from mail.mail_utils import send
from user.models import ADMIN, SUPER_ADMIN
from user.user_utils import current_user

TERMS_OF_SERVICE = 'TERMS_OF_SERVICE'

Expand Down Expand Up @@ -126,9 +131,33 @@ def mutate(self, info, name, value):
return SaveConfig(success=True)


class SendEmail(graphene.Mutation):
"""Mutation to customise foyer."""
success = graphene.Boolean(description="True if the config was saved.")

class Arguments:
subject = graphene.String(
required=True, description="The subject of the email.")
body = graphene.String(
required=True, description="The body of the email. HTML is allowed.")
recipients = graphene.String(
required=True, description="The recipients of the email. Comma separated.")

@jwt_required()
def mutate(self, info, subject, body, recipients):
code, error, user, timezone = current_user()
if not user.role in (ADMIN, SUPER_ADMIN):
raise Exception(
"Only Admin can send notification emails!")

send(recipients, subject, body)
return SendEmail(success=True)


class Mutation(graphene.ObjectType):
updateTermsOfService = UpdateTermsOfService.Field()
saveConfig = SaveConfig.Field()
sendEmail = SendEmail.Field()


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
26 changes: 26 additions & 0 deletions mail/mail_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from email.mime.text import MIMEText
from smtplib import (
SMTP_SSL as SMTP,) # this invokes the secure SMTP protocol (port 465, uses SSL)
import sys
import os
import re

from config.settings import EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_USE_TLS


def send(to, subject, content):

msg = MIMEText(content, "html")
msg["Subject"] = subject
# some SMTP servers will do this automatically, not all
msg["From"] = EMAIL_HOST_USER
msg["To"] = to

conn = SMTP(EMAIL_HOST, 465 if EMAIL_USE_TLS else 587)
conn.set_debuglevel(False)
conn.login(EMAIL_HOST_USER, EMAIL_HOST_PASSWORD)
try:
recipients = re.split(r'[,;]\s*', to)
conn.sendmail(EMAIL_HOST_USER, recipients, msg.as_string())
finally:
conn.quit()
Loading

0 comments on commit a5ba7c1

Please sign in to comment.