mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-06-16 17:49:09 +00:00
port full auth flow to cf workers
This commit is contained in:
parent
9eeb946389
commit
aad0987dce
50 changed files with 551 additions and 1167 deletions
|
@ -1,40 +0,0 @@
|
|||
package faas
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AuthLevel uint
|
||||
|
||||
const (
|
||||
AuthGuest = AuthLevel(iota)
|
||||
AuthUser
|
||||
AuthAdmin
|
||||
AuthSuper
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotAuthorized = errors.New("common/faas: session not authorized")
|
||||
)
|
||||
|
||||
func assertAuthLevel(err error, requiredAuthLevel AuthLevel, assertedAuthLevel AuthLevel) error {
|
||||
if requiredAuthLevel == assertedAuthLevel {
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// AuthMustMatch will assert the current session's authorization group/level; only can match for Guest, User, and Super.
|
||||
func AuthMustMatch(request *http.Request, authLevel AuthLevel) error {
|
||||
_, err := request.Cookie("Authorization")
|
||||
if errors.Is(err, http.ErrNoCookie) {
|
||||
// No cookie is present, assert guest.
|
||||
return assertAuthLevel(ErrNotAuthorized, authLevel, AuthGuest)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package faas
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var bounceHTML = template.Must(template.New("bounceHTML").Parse(
|
||||
`<!doctype html>
|
||||
<meta charset="utf8">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0;URL='{{.Location}}'">
|
||||
<style>
|
||||
body {
|
||||
background-color: #453E3D;
|
||||
color: #AB9B9A;
|
||||
}
|
||||
a {
|
||||
color: #AB9B9A;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Redirecting you to <a href="{{.Location}}">{{.Location}}</a>
|
||||
</p>
|
||||
`,
|
||||
))
|
||||
|
||||
type bounceData struct {
|
||||
Location string
|
||||
}
|
||||
|
||||
// Bounce will do a 303 See Other response with url.
|
||||
func Bounce(rw http.ResponseWriter, url string) {
|
||||
rw.Header().Add("location", url)
|
||||
rw.WriteHeader(303)
|
||||
bounceHTML.Execute(rw, bounceData{Location: url})
|
||||
}
|
||||
|
||||
// Stash will save the specified URL for later use in Unstash(), e.g. after an OAuth bounce
|
||||
func Stash(rw http.ResponseWriter, url string) {
|
||||
if url == "" {
|
||||
return
|
||||
}
|
||||
|
||||
cookie := http.Cookie{
|
||||
Name: "rp_stashed_url",
|
||||
Value: url,
|
||||
HttpOnly: true,
|
||||
Expires: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
rw.Header().Add("set-cookie", cookie.String())
|
||||
}
|
||||
|
||||
// Unstash will redirect/Bounce() to a previously stashed URL or the defaultURL, whichever is available.
|
||||
func Unstash(rw http.ResponseWriter, req *http.Request, defaultURL string) {
|
||||
redirectURL := defaultURL
|
||||
cookie, _ := req.Cookie("rp_stashed_url")
|
||||
|
||||
if cookie != nil && cookie.Expires.After(time.Now()) && cookie.Value != "" {
|
||||
redirectURL = cookie.Value
|
||||
}
|
||||
|
||||
unsetter := http.Cookie{
|
||||
Name: "rp_stashed_url",
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
rw.Header().Set("set-cookie", unsetter.String())
|
||||
|
||||
Bounce(rw, redirectURL)
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package faas
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/roleypoly/roleypoly/src/common/types"
|
||||
)
|
||||
|
||||
func Fingerprint(req *http.Request) types.Fingerprint {
|
||||
return types.Fingerprint{
|
||||
UserAgent: req.UserAgent(),
|
||||
ClientIP: req.RemoteAddr,
|
||||
ForwardedFor: req.Header.Get("x-forwarded-for"),
|
||||
}
|
||||
}
|
|
@ -34,3 +34,16 @@ export type PresentableGuild = {
|
|||
export type GuildEnumeration = {
|
||||
guildsList: PresentableGuild[];
|
||||
};
|
||||
|
||||
export enum UserGuildPermissions {
|
||||
User,
|
||||
Manager,
|
||||
Admin,
|
||||
}
|
||||
|
||||
export type GuildSlug = {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
permissionLevel: UserGuildPermissions;
|
||||
};
|
||||
|
|
18
src/common/types/Session.ts
Normal file
18
src/common/types/Session.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { GuildSlug } from './Guild';
|
||||
import { DiscordUser } from './User';
|
||||
|
||||
export type AuthTokenResponse = {
|
||||
access_token: string;
|
||||
token_type: 'Bearer';
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export type SessionData = {
|
||||
/** sessionID is a KSUID */
|
||||
sessionID: string;
|
||||
tokens: AuthTokenResponse;
|
||||
user: DiscordUser;
|
||||
guilds: GuildSlug[];
|
||||
};
|
|
@ -2,3 +2,4 @@ export * from './Role';
|
|||
export * from './Category';
|
||||
export * from './Guild';
|
||||
export * from './User';
|
||||
export * from './Session';
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { Role } from 'roleypoly/common/types';
|
||||
import { Role } from '../types';
|
||||
|
||||
export const evaluatePermission = (haystack: number, needle: number): boolean => {
|
||||
return (haystack & needle) === needle;
|
||||
};
|
||||
|
||||
export const hasPermission = (roles: Role[], permission: number): boolean => {
|
||||
const aggregateRoles = roles.reduce((acc, role) => acc | role.permissions, 0);
|
||||
return (aggregateRoles & permission) === permission;
|
||||
return evaluatePermission(aggregateRoles, permission);
|
||||
};
|
||||
|
||||
export const hasPermissionOrAdmin = (roles: Role[], permission: number): boolean =>
|
||||
|
|
1
src/common/utils/isBrowser.ts
Normal file
1
src/common/utils/isBrowser.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const isBrowser = () => typeof window !== 'undefined';
|
Loading…
Add table
Add a link
Reference in a new issue