Feature/new-login-page (#989)

* login page refresh

* use user_id for token identification
This commit is contained in:
Hayden 2022-02-22 11:36:58 -09:00 committed by GitHub
parent 684f39fe24
commit 177a430d8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 179 deletions

View File

@ -1,159 +1,29 @@
<template>
<v-container fill-height fluid class="d-flex justify-center align-center">
<v-card tag="section" color="background d-flex flex-column align-center" flat width="600px">
<svg
id="bbc88faa-5a3b-49cf-bdbb-6c9ab11be594"
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 728 754.88525"
style="max-height: 100px"
class="mt-2"
>
<rect
x="514.67011"
y="302.6311"
width="33"
height="524"
transform="translate(-458.65432 311.24592) rotate(-33.25976)"
fill="#e6e6e6"
/>
<path
d="M335.58256,171.60615l63.84438,97.34271a8.5,8.5,0,0,1-14.21528,9.32341L311.81484,166.36508a60.62682,60.62682,0,0,0-29.14936,4.78729L362.63446,293.08a8.5,8.5,0,0,1-14.21528,9.3234l-79.969-121.92766A60.62685,60.62685,0,0,0,252.44516,205.304L325.842,317.2112a8.5,8.5,0,0,1-14.21528,9.3234l-63.84438-97.3427c-1.6398,27.14157,7.20944,59.3114,26.60329,88.881,36.04421,54.95614,94.83957,80.109,131.32307,56.18052s36.8396-87.87721.79539-142.83336C387.11022,201.85046,361.13005,180.91634,335.58256,171.60615Z"
transform="translate(-236 -72.55738)"
fill="#e6e6e6"
/>
<rect x="256" y="204" width="33" height="524" fill="#e6e6e6" />
<ellipse cx="272" cy="119" rx="79" ry="119" fill="#e6e6e6" />
<ellipse cx="272" cy="119" rx="65" ry="97.91139" fill="#ccc" />
<ellipse cx="364" cy="734" rx="364" ry="20.88525" fill="#e6e6e6" />
<path
d="M815.26782,806.25045a1162.796,1162.796,0,0,0-412.53564,0A15.04906,15.04906,0,0,1,385,791.45826V604.55738H833V791.45826A15.04906,15.04906,0,0,1,815.26782,806.25045Z"
transform="translate(-236 -72.55738)"
fill="#e58325"
/>
<path
d="M792,466.55738a92.85808,92.85808,0,0,0-30.39526,5.0863,179.055,179.055,0,0,0-324.4441-1.63928,93.00486,93.00486,0,1,0,12.16987,174.74988,179.02647,179.02647,0,0,0,300.7481-2.16382A93.00664,93.00664,0,1,0,792,466.55738Z"
transform="translate(-236 -72.55738)"
fill="#e58325"
/>
<path
d="M421,546.55738h-2A178.40222,178.40222,0,0,1,436.24707,469.572l1.80762.85644A176.41047,176.41047,0,0,0,421,546.55738Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M779,546.55738h-2a176.52632,176.52632,0,0,0-16.29395-74.501l1.81641-.83789A178.51046,178.51046,0,0,1,779,546.55738Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M385.24121,691.52808l-.48242-1.94141c.56445-.13964,57.40332-14.09961,140.70019-21.02636,76.88086-6.39258,192.68653-7.93457,307.78516,21.02734l-.48828,1.93945C717.93945,662.63746,602.38672,664.17261,525.667,670.55054,442.519,677.46167,385.8042,691.38843,385.24121,691.52808Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M385.24121,718.52808l-.48242-1.94141c.56445-.13964,57.40332-14.09961,140.70019-21.02636,76.88086-6.39258,192.68653-7.93457,307.78516,21.02734l-.48828,1.93945C717.93945,689.63746,602.38672,691.17456,525.667,697.55054,442.519,704.46167,385.8042,718.38843,385.24121,718.52808Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M385.24121,745.52808l-.48242-1.94141c.56445-.13964,57.40332-14.09961,140.70019-21.02636,76.88086-6.39258,192.68653-7.93457,307.78516,21.02734l-.48828,1.93945C717.93945,716.63746,602.38672,718.17456,525.667,724.55054,442.519,731.46167,385.8042,745.38843,385.24121,745.52808Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M753.26693,598.71334,729.03658,590.475l23.26113-118.72871-15.992-1.45382c-15.594,11.96435-36.35984,16.65481-62.99891,13.08438l42.64542,64.45274-21.745,15.34942-69.368-83.20523A20.866,20.866,0,0,1,620,466.61227v0a20.866,20.866,0,0,1,15.0905-20.05076L709.16769,425.224l86.74466,9.69214c13.11467,19.99417,13.62744,33.89954-6.33645,37.911Z"
transform="translate(-236 -72.55738)"
fill="#2f2e41"
/>
<path
d="M728.46691,644.90106h0a15.95869,15.95869,0,0,1-13.86555-21.711l12.046-30.97551c6.11869-11.59112,14.5164-10.14011,24.43261,0l4.84611,14.21526a9.17534,9.17534,0,0,1-.53485,7.176L743.64973,636.306A15.95871,15.95871,0,0,1,728.46691,644.90106Z"
transform="translate(-236 -72.55738)"
fill="#2f2e41"
/>
<path
d="M697.15218,604.33834h0a15.95869,15.95869,0,0,1-13.86555-21.711l12.046-30.97551c6.11869-11.59113,14.51641-10.14012,24.43261,0l4.84611,14.21525a9.17537,9.17537,0,0,1-.53485,7.176L712.335,595.74331A15.9587,15.9587,0,0,1,697.15218,604.33834Z"
transform="translate(-236 -72.55738)"
fill="#2f2e41"
/>
<circle cx="476.55994" cy="212.13062" r="27.13799" fill="#ffb9b9" />
<polygon
points="518.721 250.415 481.406 269.799 473.652 234.907 499.336 218.915 518.721 250.415"
fill="#ffb9b9"
/>
<path
d="M799.7892,439.76224c-37.23393-11.24605-71.01788-17.07317-95.46758-8.23832,8.42738-23.70818-7.12737-59.91146-24.23035-96.9214,7.37949-9.64677,19.14576-13.38347,32.46867-15.02282,14.5769,10.5844,24.74122,3.79091,32.46867-12.59978,16.85358-.67652,33.095,5.29186,48.94531,15.50743C781.58355,362.17339,783.814,401.25293,799.7892,439.76224Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M703.837,437.33921c-5.87952,3.4656-11.3058,9.30325-16.47664,16.47664-8.73817-5.349-16.42816-11.439-22.48592-18.68294a40.01122,40.01122,0,0,1-7.33032-37.42892l16.56053-53.82173a23.60967,23.60967,0,0,1,7.67755-11.38054l2.18592-1.776,21.80731,41.19159-21.80731,40.707C686.73356,420.03892,694.88267,428.6031,703.837,437.33921Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M711.343,478.37478h0a14.00455,14.00455,0,0,1-19.66674-10.71872L688.072,442.98155l12.59979-6.7845,15.9909,20.93355A14.00455,14.00455,0,0,1,711.343,478.37478Z"
transform="translate(-236 -72.55738)"
fill="#ffb9b9"
/>
<path
d="M739.94024,283.50047l-4.63369.13763-12.853-18.20724c-16.46951,1.70257-29.96494,8.858-41.38524,19.81828l-1.15795-7.71966a29.10153,29.10153,0,0,1,22.90286-32.81892h.00006a29.10153,29.10153,0,0,1,34.57213,23.6573Z"
transform="translate(-236 -72.55738)"
fill="#2f2e41"
/>
<path
d="M687.82806,453.82563v0a14.00456,14.00456,0,0,1,10.71872-19.66675l24.67452-3.60414,6.7845,12.59978L709.07224,459.1454A14.00455,14.00455,0,0,1,687.82806,453.82563Z"
transform="translate(-236 -72.55738)"
fill="#ffb9b9"
/>
<path
d="M804.49034,431.38118c-23.4754,1.82279-49.10633,9.14326-75.93837,19.527a37.12074,37.12074,0,0,0-8.23832-21.80731c24.37008-6.41874,46.48406-13.95144,60.09127-25.68417L772.1666,341.387l17.93046-20.35349,3.09274,1.6136a20.65228,20.65228,0,0,1,10.4691,13.14326c7.57071,29.449,10.93351,57.66486,8.62195,84.21782A10.47079,10.47079,0,0,1,804.49034,431.38118Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<path
d="M331.88594,800.6692q-32.74851,20.483-65.49722-.01716a4.441,4.441,0,0,1-2.10125-4.0963l6.81241-88.56136h55.10049l7.78288,88.5302A4.44,4.44,0,0,1,331.88594,800.6692Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<ellipse cx="62.39599" cy="636.43883" rx="27.80438" ry="10.01827" fill="#3f3d56" />
<path
d="M320.18941,705.61437q-21.73251,15.28772-42.07674,0V689.58514h42.07674Z"
transform="translate(-236 -72.55738)"
fill="#e58325"
/>
<ellipse cx="63.15104" cy="617.02776" rx="21.03837" ry="8.01462" fill="#e58325" />
<ellipse cx="64.15287" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="73.61397" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="68.88342" cy="618.39121" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="49.96121" cy="618.39121" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="54.69176" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="59.42232" cy="619.57385" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<path
d="M936.88594,800.6692q-32.74851,20.483-65.49722-.01716a4.441,4.441,0,0,1-2.10125-4.0963l6.81241-88.56136h55.10049l7.78288,88.5302A4.44,4.44,0,0,1,936.88594,800.6692Z"
transform="translate(-236 -72.55738)"
fill="#3f3d56"
/>
<ellipse cx="667.39599" cy="636.43883" rx="27.80438" ry="10.01827" fill="#3f3d56" />
<path
d="M925.18941,705.61437q-21.73251,15.28772-42.07674,0V689.58514h42.07674Z"
transform="translate(-236 -72.55738)"
fill="#e58325"
/>
<ellipse cx="668.15104" cy="617.02776" rx="21.03837" ry="8.01462" fill="#e58325" />
<ellipse cx="669.15287" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="678.61397" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="673.88342" cy="618.39121" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="654.96121" cy="618.39121" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="659.69176" cy="616.02594" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
<ellipse cx="664.42232" cy="619.57385" rx="2.00365" ry="1.00183" fill="#e6e6e6" />
</svg>
<v-card-title class="headline justify-center"> Sign In To Mealie </v-card-title>
<BaseDivider />
<v-container fill-height fluid class="d-flex justify-center align-center" style="background: #f5f8fa">
<v-card tag="section" class="d-flex flex-column align-center" width="600px">
<v-toolbar width="100%" color="primary" class="d-flex justify-center mb-4" dark>
<v-toolbar-title class="headline text-h4"> Mealie </v-toolbar-title>
</v-toolbar>
<div class="icon-container">
<v-divider class="icon-divider"></v-divider>
<v-avatar size="102" color="grey lighten-2">
<v-avatar class="pa-2 icon-avatar" color="white" size="100">
<svg class="icon-primary" style="width: 100px; height: 100px" viewBox="0 0 24 24">
<path
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
/>
</svg>
</v-avatar>
</v-avatar>
</div>
<v-card-title class="headline justify-center pb-1"> Sign In </v-card-title>
<v-card-text>
<v-form @submit.prevent="authenticate()">
<v-text-field
v-model="form.email"
:prepend-inner-icon="$globals.icons.email"
filled
rounded
autofocus
@ -165,6 +35,7 @@
<v-text-field
id="password"
v-model="form.password"
:prepend-inner-icon="$globals.icons.lock"
filled
rounded
class="rounded-lg"
@ -172,8 +43,8 @@
label="Password"
type="password"
/>
<v-checkbox v-model="form.remember" class="ml-2 mt-n4" label="Remember Me"></v-checkbox>
<v-card-actions class="justify-center">
<v-checkbox v-model="form.remember" class="ml-2 mt-n2" label="Remember Me"></v-checkbox>
<v-card-actions class="justify-center pt-0">
<div class="max-button">
<v-btn :loading="loggingIn" color="primary" type="submit" large rounded class="rounded-xl" block>
{{ $t("user.login") }}
@ -187,6 +58,38 @@
<v-btn v-else text disabled> {{ $t("user.invite-only") }} </v-btn>
<v-btn class="mr-auto" text to="/forgot-password"> {{ $t("user.reset-password") }} </v-btn>
</v-card-actions>
<v-divider></v-divider>
<v-card-text class="d-flex justify-center">
<div
v-for="link in [
{
text: 'Sponsor',
icon: $globals.icons.heart,
href: 'https://github.com/sponsors/hay-kot',
},
{
text: 'GitHub',
icon: $globals.icons.github,
href: 'https://github.com/hay-kot/mealie',
},
{
text: 'Docs',
icon: $globals.icons.folderOutline,
href: 'https://docs.mealie.io/',
},
]"
:key="link.text"
>
<v-btn text :href="link.href" target="_blank">
<v-icon left>
{{ link.icon }}
</v-icon>
{{ link.text }}
</v-btn>
</div>
</v-card-text>
</v-card>
</v-container>
</template>
@ -259,4 +162,27 @@ export default defineComponent({
.max-button {
width: 300px;
}
.icon-primary {
fill: var(--v-primary-base);
}
.icon-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
position: relative;
margin-top: 3.5rem;
}
.icon-divider {
width: 100%;
margin-bottom: -3.5rem;
}
.icon-avatar {
border-color: rgba(0, 0, 0, 0.12);
border: 2px;
}
</style>

View File

@ -36,18 +36,17 @@ async def is_logged_in(token: str = Depends(oauth2_scheme_soft_fail), session=De
"""
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
username: str = payload.get("sub")
user_id: str = payload.get("sub")
long_token: str = payload.get("long_token")
if long_token is not None:
try:
user = validate_long_live_token(session, token, payload.get("id"))
if user:
if validate_long_live_token(session, token, payload.get("id")):
return True
except Exception:
return False
return username is not None
return user_id is not None
except Exception:
return False
@ -61,37 +60,38 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(
)
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
username: str = payload.get("sub")
user_id: str = payload.get("sub")
long_token: str = payload.get("long_token")
if long_token is not None:
return validate_long_live_token(session, token, payload.get("id"))
if username is None:
if user_id is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
token_data = TokenData(user_id=user_id)
except JWTError as e:
raise credentials_exception from e
db = get_repositories(session)
repos = get_repositories(session)
user = repos.users.get(token_data.user_id, "id", any_case=False)
user = db.users.get(token_data.username, "email", any_case=True)
if user is None:
raise credentials_exception
return user
async def get_admin_user(current_user=Depends(get_current_user)) -> PrivateUser:
async def get_admin_user(current_user: PrivateUser = Depends(get_current_user)) -> PrivateUser:
if not current_user.admin:
raise HTTPException(status.HTTP_403_FORBIDDEN)
return current_user
def validate_long_live_token(session: Session, client_token: str, id: int) -> PrivateUser:
db = get_repositories(session)
repos = get_repositories(session)
tokens: list[LongLiveTokenInDB] = db.api_tokens.get(id, "user_id", limit=9999)
tokens: list[LongLiveTokenInDB] = repos.api_tokens.get(id, "user_id", limit=9999)
for token in tokens:
token: LongLiveTokenInDB
@ -110,8 +110,8 @@ def validate_file_token(token: Optional[str] = None) -> Path:
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
file_path = Path(payload.get("file"))
except JWTError:
raise credentials_exception
except JWTError as e:
raise credentials_exception from e
return file_path
@ -127,8 +127,8 @@ def validate_recipe_token(token: Optional[str] = None) -> str:
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
slug = payload.get("slug")
except JWTError:
raise credentials_exception
except JWTError as e:
raise credentials_exception from e
return slug

View File

@ -48,10 +48,7 @@ class MealieAuthToken(BaseModel):
@public_router.post("/token")
def get_token(
data: CustomOAuth2Form = Depends(),
session: Session = Depends(generate_session),
):
def get_token(data: CustomOAuth2Form = Depends(), session: Session = Depends(generate_session)):
email = data.username
password = data.password
@ -64,12 +61,12 @@ def get_token(
)
duration = timedelta(days=14) if data.remember_me else None
access_token = security.create_access_token(dict(sub=user.email), duration)
access_token = security.create_access_token(dict(sub=str(user.id)), duration)
return MealieAuthToken.respond(access_token)
@user_router.get("/refresh")
async def refresh_token(current_user: PrivateUser = Depends(get_current_user)):
"""Use a valid token to get another token"""
access_token = security.create_access_token(data=dict(sub=current_user.email))
access_token = security.create_access_token(data=dict(sub=str(current_user.id)))
return MealieAuthToken.respond(access_token)

View File

@ -66,7 +66,7 @@ class UserController(BaseUserController):
self.repos.users.update(item_id, new_data.dict())
if self.user.id == item_id:
access_token = security.create_access_token(data=dict(sub=new_data.email))
access_token = security.create_access_token(data=dict(sub=str(self.user.id)))
return {"access_token": access_token, "token_type": "bearer"}
@user_router.put("/{item_id}/password")

View File

@ -1,6 +1,6 @@
from typing import Optional
from pydantic import BaseModel
from pydantic import UUID4, BaseModel
from pydantic.types import constr
@ -10,4 +10,5 @@ class Token(BaseModel):
class TokenData(BaseModel):
user_id: Optional[UUID4]
username: Optional[constr(to_lower=True, strip_whitespace=True)] = None