diff options
| author | 2022-02-05 16:50:11 +0400 | |
|---|---|---|
| committer | 2022-02-05 17:20:40 +0400 | |
| commit | 134b2f70e4cf947744f1b061766bb37fe616ad65 (patch) | |
| tree | ef6b95bc5a78528d91ae969f3cfd00bc8e5be8ed /backend/routes | |
| parent | Add Helper Functions For Managing Roles (diff) | |
Overhaul Scope System
Adds discord role support to the pre-existing scopes system to power
more complex access permissions.
Signed-off-by: Hassan Abouelela <[email protected]>
Diffstat (limited to 'backend/routes')
| -rw-r--r-- | backend/routes/auth/authorize.py | 12 | ||||
| -rw-r--r-- | backend/routes/discord.py | 83 | ||||
| -rw-r--r-- | backend/routes/forms/submit.py | 2 | ||||
| -rw-r--r-- | backend/routes/roles.py | 36 | 
4 files changed, 92 insertions, 41 deletions
diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index d4587f0..42fb3ec 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -17,7 +17,7 @@ from starlette.requests import Request  from backend import constants  from backend.authentication.user import User  from backend.constants import SECRET_KEY -from backend.discord import fetch_bearer_token, fetch_user_details +from backend.discord import fetch_bearer_token, fetch_user_details, get_member  from backend.route import Route  from backend.validation import ErrorMessage, api @@ -34,8 +34,8 @@ class AuthorizeResponse(BaseModel):  async def process_token( -        bearer_token: dict, -        request: Request +    bearer_token: dict, +    request: Request  ) -> Union[AuthorizeResponse, AUTH_FAILURE]:      """Post a bearer token to Discord, and return a JWT and username."""      interaction_start = datetime.datetime.now() @@ -46,6 +46,9 @@ async def process_token(          AUTH_FAILURE.delete_cookie("token")          return AUTH_FAILURE +    user_id = user_details["id"] +    member = await get_member(request.state.db, user_id, force_refresh=True) +      max_age = datetime.timedelta(seconds=int(bearer_token["expires_in"]))      token_expiry = interaction_start + max_age @@ -53,11 +56,12 @@ async def process_token(          "token": bearer_token["access_token"],          "refresh": bearer_token["refresh_token"],          "user_details": user_details, +        "in_guild": bool(member),          "expiry": token_expiry.isoformat()      }      token = jwt.encode(data, SECRET_KEY, algorithm="HS256") -    user = User(token, user_details) +    user = User(token, user_details, member)      response = responses.JSONResponse({          "username": user.display_name, diff --git a/backend/routes/discord.py b/backend/routes/discord.py new file mode 100644 index 0000000..a980d94 --- /dev/null +++ b/backend/routes/discord.py @@ -0,0 +1,83 @@ +"""Routes which directly interact with discord related data.""" + +import pydantic +from spectree import Response +from starlette.authentication import requires +from starlette.responses import JSONResponse +from starlette.routing import Request + +from backend import discord, models, route +from backend.validation import ErrorMessage, OkayResponse, api + +NOT_FOUND_EXCEPTION = JSONResponse( +    {"error": "Could not find the requested resource in the guild or cache."}, status_code=404 +) + + +class RolesRoute(route.Route): +    """Refreshes the roles database.""" + +    name = "roles" +    path = "/roles" + +    class RolesResponse(pydantic.BaseModel): +        """A list of all roles on the configured server.""" + +        roles: list[models.DiscordRole] + +    @requires(["authenticated", "admin"]) +    @api.validate( +        resp=Response(HTTP_200=OkayResponse), +        tags=["roles"] +    ) +    async def patch(self, request: Request) -> JSONResponse: +        """Refresh the roles database.""" +        roles = await discord.get_roles(request.state.db, force_refresh=True) + +        return JSONResponse( +            {"status": "ok"}, +        ) + + +class MemberRoute(route.Route): +    """Retrieve information about a server member.""" + +    name = "member" +    path = "/member" + +    class MemberRequest(pydantic.BaseModel): +        """An ID of the member to update.""" + +        user_id: str + +    @requires(["authenticated", "admin"]) +    @api.validate( +        resp=Response(HTTP_200=models.DiscordMember, HTTP_400=ErrorMessage), +        json=MemberRequest, +        tags=["auth"] +    ) +    async def delete(self, request: Request): +        """Force a resync of the cache for the given user.""" +        body = await request.json() +        member = await discord.get_member(request.state.db, body["user_id"], force_refresh=True) + +        if member: +            return JSONResponse(member.dict()) +        else: +            return NOT_FOUND_EXCEPTION + +    @requires(["authenticated", "admin"]) +    @api.validate( +        resp=Response(HTTP_200=models.DiscordMember, HTTP_400=ErrorMessage), +        json=MemberRequest, +        tags=["auth"] +    ) +    async def get(self, request: Request): +        """Get a user's roles on the configured server.""" +        body = await request.json() +        member = await discord.get_member(request.state.db, body["user_id"]) + +        if member: +            return JSONResponse(member.dict()) +        else: +            return NOT_FOUND_EXCEPTION diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 95e30b0..baf403d 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -83,7 +83,7 @@ class SubmitForm(Route):          try:              if hasattr(request.user, User.refresh_data.__name__):                  old = request.user.token -                await request.user.refresh_data() +                await request.user.refresh_data(request.state.db)                  if old != request.user.token:                      try: diff --git a/backend/routes/roles.py b/backend/routes/roles.py deleted file mode 100644 index b18a04b..0000000 --- a/backend/routes/roles.py +++ /dev/null @@ -1,36 +0,0 @@ -import starlette.background -from pymongo.database import Database -from spectree import Response -from starlette.authentication import requires -from starlette.responses import JSONResponse -from starlette.routing import Request - -from backend import discord, route -from backend.validation import OkayResponse, api - - -async def refresh_roles(database: Database) -> None: -    """Connect to the discord API and refresh the roles database.""" -    roles = await discord.get_role_info() -    roles_collection = database.get_collection("roles") -    roles_collection.drop() -    roles_collection.insert_many([role.dict() for role in roles]) - - -class RolesRoute(route.Route): -    """Refreshes the roles database.""" - -    name = "roles" -    path = "/roles" - -    @requires(["authenticated", "admin"]) -    @api.validate( -        resp=Response(HTTP_200=OkayResponse), -        tags=["roles"] -    ) -    async def patch(self, request: Request) -> JSONResponse: -        """Refresh the roles database.""" -        return JSONResponse( -            {"status": "ok"}, -            background=starlette.background.BackgroundTask(refresh_roles, request.state.db) -        )  |