satellite/console: use cookie based auth scheme
Change-Id: I143b56f49fa9028ec172db8c29fd93577c3e7878
This commit is contained in:
parent
b678b55f83
commit
c636b06191
@ -23,14 +23,16 @@ import (
|
||||
|
||||
func newConsoleEndpoints(address string) *consoleEndpoints {
|
||||
return &consoleEndpoints{
|
||||
client: http.DefaultClient,
|
||||
base: "http://" + address,
|
||||
client: http.DefaultClient,
|
||||
base: "http://" + address,
|
||||
cookieName: "_tokenKey",
|
||||
}
|
||||
}
|
||||
|
||||
type consoleEndpoints struct {
|
||||
client *http.Client
|
||||
base string
|
||||
client *http.Client
|
||||
base string
|
||||
cookieName string
|
||||
}
|
||||
|
||||
func (ce *consoleEndpoints) appendPath(suffix string) string {
|
||||
@ -309,8 +311,12 @@ func (ce *consoleEndpoints) getProject(token string) (string, error) {
|
||||
q.Add("query", `query {myProjects{id}}`)
|
||||
request.URL.RawQuery = q.Encode()
|
||||
|
||||
request.AddCookie(&http.Cookie{
|
||||
Name: ce.cookieName,
|
||||
Value: token,
|
||||
})
|
||||
|
||||
request.Header.Add("Content-Type", "application/graphql")
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
var getProjects struct {
|
||||
MyProjects []struct {
|
||||
@ -341,8 +347,12 @@ func (ce *consoleEndpoints) createProject(token string) (string, error) {
|
||||
return "", errs.Wrap(err)
|
||||
}
|
||||
|
||||
request.AddCookie(&http.Cookie{
|
||||
Name: ce.cookieName,
|
||||
Value: token,
|
||||
})
|
||||
|
||||
request.Header.Add("Content-Type", "application/graphql")
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
var createProject struct {
|
||||
CreateProject struct {
|
||||
@ -370,8 +380,12 @@ func (ce *consoleEndpoints) createAPIKey(token, projectID string) (string, error
|
||||
return "", errs.Wrap(err)
|
||||
}
|
||||
|
||||
request.AddCookie(&http.Cookie{
|
||||
Name: ce.cookieName,
|
||||
Value: token,
|
||||
})
|
||||
|
||||
request.Header.Add("Content-Type", "application/graphql")
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
var createAPIKey struct {
|
||||
CreateAPIKey struct {
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"storj.io/storj/private/post"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
)
|
||||
|
||||
@ -24,24 +25,26 @@ var ErrAuthAPI = errs.Class("console auth api error")
|
||||
// Auth is an api controller that exposes all auth functionality.
|
||||
type Auth struct {
|
||||
log *zap.Logger
|
||||
service *console.Service
|
||||
mailService *mailservice.Service
|
||||
ExternalAddress string
|
||||
LetUsKnowURL string
|
||||
TermsAndConditionsURL string
|
||||
ContactInfoURL string
|
||||
service *console.Service
|
||||
mailService *mailservice.Service
|
||||
cookieAuth *consolewebauth.CookieAuth
|
||||
}
|
||||
|
||||
// NewAuth is a constructor for api auth controller.
|
||||
func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string) *Auth {
|
||||
func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, cookieAuth *consolewebauth.CookieAuth, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string) *Auth {
|
||||
return &Auth{
|
||||
log: log,
|
||||
service: service,
|
||||
mailService: mailService,
|
||||
ExternalAddress: externalAddress,
|
||||
LetUsKnowURL: letUsKnowURL,
|
||||
TermsAndConditionsURL: termsAndConditionsURL,
|
||||
ContactInfoURL: contactInfoURL,
|
||||
service: service,
|
||||
mailService: mailService,
|
||||
cookieAuth: cookieAuth,
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +71,8 @@ func (a *Auth) Token(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
a.cookieAuth.SetTokenCookie(w, token)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(token)
|
||||
if err != nil {
|
||||
|
50
satellite/console/consoleweb/consolewebauth/auth.go
Normal file
50
satellite/console/consoleweb/consolewebauth/auth.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package consolewebauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CookieSettings variable cookie settings.
|
||||
type CookieSettings struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
// CookieAuth handles cookie authorization.
|
||||
type CookieAuth struct {
|
||||
settings CookieSettings
|
||||
}
|
||||
|
||||
// NewCookieAuth create new cookie authorization with provided settings.
|
||||
func NewCookieAuth(settings CookieSettings) *CookieAuth {
|
||||
return &CookieAuth{
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
// GetToken retrieves token from request.
|
||||
func (auth *CookieAuth) GetToken(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie(auth.settings.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
// SetTokenCookie sets parametrized token cookie that is not accessible from js.
|
||||
func (auth *CookieAuth) SetTokenCookie(w http.ResponseWriter, token string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: auth.settings.Name,
|
||||
Value: token,
|
||||
Path: auth.settings.Path,
|
||||
// TODO: get expiration from token
|
||||
Expires: time.Now().Add(time.Hour * 24),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
}
|
@ -32,15 +32,13 @@ import (
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/referrals"
|
||||
)
|
||||
|
||||
const (
|
||||
authorization = "Authorization"
|
||||
contentType = "Content-Type"
|
||||
|
||||
authorizationBearer = "Bearer "
|
||||
contentType = "Content-Type"
|
||||
|
||||
applicationJSON = "application/json"
|
||||
applicationGraphql = "application/graphql"
|
||||
@ -86,8 +84,9 @@ type Server struct {
|
||||
mailService *mailservice.Service
|
||||
referralsService *referrals.Service
|
||||
|
||||
listener net.Listener
|
||||
server http.Server
|
||||
listener net.Listener
|
||||
server http.Server
|
||||
cookieAuth *consolewebauth.CookieAuth
|
||||
|
||||
stripePublicKey string
|
||||
|
||||
@ -117,6 +116,11 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
||||
|
||||
logger.Sugar().Debugf("Starting Satellite UI on %s...", server.listener.Addr().String())
|
||||
|
||||
server.cookieAuth = consolewebauth.NewCookieAuth(consolewebauth.CookieSettings{
|
||||
Name: "_tokenKey",
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
if server.config.ExternalAddress != "" {
|
||||
if !strings.HasSuffix(server.config.ExternalAddress, "/") {
|
||||
server.config.ExternalAddress += "/"
|
||||
@ -128,11 +132,12 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
||||
router := mux.NewRouter()
|
||||
fs := http.FileServer(http.Dir(server.config.StaticDir))
|
||||
|
||||
router.HandleFunc("/api/v0/graphql", server.grapqlHandler)
|
||||
router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
|
||||
router.HandleFunc("/populate-promotional-coupons", server.populatePromotionalCoupons).Methods(http.MethodPost)
|
||||
router.HandleFunc("/robots.txt", server.seoHandler)
|
||||
|
||||
router.Handle("/api/v0/graphql", server.withAuth(http.HandlerFunc(server.grapqlHandler)))
|
||||
|
||||
router.Handle(
|
||||
"/api/v0/projects/{id}/usage-limits",
|
||||
server.withAuth(http.HandlerFunc(server.projectUsageLimitsHandler)),
|
||||
@ -143,7 +148,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
||||
referralsRouter.Handle("/tokens", server.withAuth(http.HandlerFunc(referralsController.GetTokens))).Methods(http.MethodGet)
|
||||
referralsRouter.HandleFunc("/register", referralsController.Register).Methods(http.MethodPost)
|
||||
|
||||
authController := consoleapi.NewAuth(logger, service, mailService, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
|
||||
authController := consoleapi.NewAuth(logger, service, mailService, server.cookieAuth, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
|
||||
authRouter := router.PathPrefix("/api/v0/auth").Subrouter()
|
||||
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet)
|
||||
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.UpdateAccount))).Methods(http.MethodPatch)
|
||||
@ -255,19 +260,29 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// authMiddlewareHandler performs initial authorization before every request.
|
||||
func (server *Server) withAuth(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
token := getToken(r)
|
||||
var ctx context.Context
|
||||
|
||||
ctx = auth.WithAPIKey(ctx, []byte(token))
|
||||
auth, err := server.service.Authorize(ctx)
|
||||
if err != nil {
|
||||
ctx = console.WithAuthFailure(ctx, err)
|
||||
} else {
|
||||
ctx = console.WithAuth(ctx, auth)
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
ctxWithAuth := func(ctx context.Context) context.Context {
|
||||
token, err := server.cookieAuth.GetToken(r)
|
||||
if err != nil {
|
||||
return console.WithAuthFailure(ctx, err)
|
||||
}
|
||||
|
||||
ctx = auth.WithAPIKey(ctx, []byte(token))
|
||||
|
||||
auth, err := server.service.Authorize(ctx)
|
||||
if err != nil {
|
||||
return console.WithAuthFailure(ctx, err)
|
||||
}
|
||||
|
||||
return console.WithAuth(ctx, auth)
|
||||
}
|
||||
|
||||
ctx = ctxWithAuth(r.Context())
|
||||
|
||||
handler.ServeHTTP(w, r.Clone(ctx))
|
||||
})
|
||||
}
|
||||
@ -278,13 +293,13 @@ func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Re
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
tokenCookie, err := r.Cookie("_tokenKey")
|
||||
token, err := server.cookieAuth.GetToken(r)
|
||||
if err != nil {
|
||||
server.serveError(w, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
auth, err := server.service.Authorize(auth.WithAPIKey(ctx, []byte(tokenCookie.Value)))
|
||||
auth, err := server.service.Authorize(auth.WithAPIKey(ctx, []byte(token)))
|
||||
if err != nil {
|
||||
server.serveError(w, http.StatusUnauthorized)
|
||||
return
|
||||
@ -376,34 +391,39 @@ func (server *Server) createRegistrationTokenHandler(w http.ResponseWriter, r *h
|
||||
|
||||
// populatePromotionalCoupons is web app http handler function for populating promotional coupons.
|
||||
func (server *Server) populatePromotionalCoupons(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
defer mon.Task()(&ctx)(nil)
|
||||
w.Header().Set(contentType, applicationJSON)
|
||||
var err error
|
||||
var ctx context.Context
|
||||
|
||||
var response struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
defer func() {
|
||||
err := json.NewEncoder(w).Encode(&response)
|
||||
if err != nil {
|
||||
handleError := func(status int, err error) {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set(contentType, applicationJSON)
|
||||
|
||||
var response struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
response.Error = err.Error()
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
server.log.Error("failed to write json error response", zap.Error(Error.Wrap(err)))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ctx = r.Context()
|
||||
|
||||
equality := subtle.ConstantTimeCompare(
|
||||
[]byte(r.Header.Get("Authorization")),
|
||||
[]byte(server.config.AuthToken),
|
||||
)
|
||||
if equality != 1 {
|
||||
w.WriteHeader(401)
|
||||
response.Error = "unauthorized"
|
||||
handleError(http.StatusUnauthorized, errs.New("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := server.service.Payments().PopulatePromotionalCoupons(ctx)
|
||||
if err != nil {
|
||||
response.Error = err.Error()
|
||||
if err = server.service.Payments().PopulatePromotionalCoupons(ctx); err != nil {
|
||||
handleError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -580,16 +600,6 @@ func (server *Server) grapqlHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
token := getToken(r)
|
||||
|
||||
ctx = auth.WithAPIKey(ctx, []byte(token))
|
||||
auth, err := server.service.Authorize(ctx)
|
||||
if err != nil {
|
||||
ctx = console.WithAuthFailure(ctx, err)
|
||||
} else {
|
||||
ctx = console.WithAuth(ctx, auth)
|
||||
}
|
||||
|
||||
rootObject := make(map[string]interface{})
|
||||
|
||||
rootObject["origin"] = server.config.ExternalAddress
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
@ -38,20 +37,6 @@ type graphqlJSON struct {
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
// getToken retrieves token from request
|
||||
func getToken(req *http.Request) string {
|
||||
value := req.Header.Get(authorization)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(value, authorizationBearer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return value[len(authorizationBearer):]
|
||||
}
|
||||
|
||||
// getQuery retrieves graphql query from request
|
||||
func getQuery(w http.ResponseWriter, req *http.Request) (query graphqlJSON, err error) {
|
||||
switch req.Method {
|
||||
|
@ -22,7 +22,7 @@ export class AuthHttpApi {
|
||||
*/
|
||||
public async resendEmail(userId: string): Promise<void> {
|
||||
const path = `${this.ROOT_PATH}/resend-email/${userId}`;
|
||||
const response = await this.http.post(path, userId, false);
|
||||
const response = await this.http.post(path, userId);
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
@ -43,7 +43,7 @@ export class AuthHttpApi {
|
||||
email: email,
|
||||
password: password,
|
||||
};
|
||||
const response = await this.http.post(path, JSON.stringify(body), false);
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
@ -63,7 +63,7 @@ export class AuthHttpApi {
|
||||
*/
|
||||
public async forgotPassword(email: string): Promise<void> {
|
||||
const path = `${this.ROOT_PATH}/forgot-password/${email}`;
|
||||
await this.http.post(path, email, false);
|
||||
await this.http.post(path, email);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,7 +78,7 @@ export class AuthHttpApi {
|
||||
fullName: userInfo.fullName,
|
||||
shortName: userInfo.shortName,
|
||||
};
|
||||
const response = await this.http.patch(path, JSON.stringify(body), true);
|
||||
const response = await this.http.patch(path, JSON.stringify(body));
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
@ -93,7 +93,7 @@ export class AuthHttpApi {
|
||||
*/
|
||||
public async get(): Promise<User> {
|
||||
const path = `${this.ROOT_PATH}/account`;
|
||||
const response = await this.http.get(path, true);
|
||||
const response = await this.http.get(path);
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
@ -114,7 +114,7 @@ export class AuthHttpApi {
|
||||
password: password,
|
||||
newPassword: newPassword,
|
||||
};
|
||||
const response = await this.http.post(path, JSON.stringify(body), true);
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
@ -140,7 +140,7 @@ export class AuthHttpApi {
|
||||
const body = {
|
||||
password: password,
|
||||
};
|
||||
const response = await this.http.post(path, JSON.stringify(body), true);
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
@ -174,7 +174,7 @@ export class AuthHttpApi {
|
||||
partnerId: user.partnerId ? user.partnerId : '',
|
||||
};
|
||||
|
||||
const response = await this.http.post(path, JSON.stringify(body), false);
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new ErrorUnauthorized('we are unable to create your account. This is an invite-only alpha, please join our waitlist to receive an invitation');
|
||||
@ -204,7 +204,7 @@ export class AuthHttpApi {
|
||||
email: user.email,
|
||||
};
|
||||
|
||||
const response = await this.http.post(path, JSON.stringify(body), false);
|
||||
const response = await this.http.post(path, JSON.stringify(body));
|
||||
if (!response.ok) {
|
||||
if (response.status === 400) {
|
||||
throw new Error('we are unable to create your account. This is an invite-only alpha, please join our waitlist to receive an invitation');
|
||||
|
@ -121,7 +121,7 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
*/
|
||||
public async getLimits(projectId): Promise<ProjectLimits> {
|
||||
const path = `${this.ROOT_PATH}/${projectId}/usage-limits`;
|
||||
const response = await this.http.get(path, true);
|
||||
const response = await this.http.get(path);
|
||||
|
||||
if (response.ok) {
|
||||
const limits = await response.json();
|
||||
|
@ -19,7 +19,7 @@ export class ReferralHttpApi {
|
||||
*/
|
||||
public async getTokens(): Promise<string[]> {
|
||||
const path = `${this.ROOT_PATH}/tokens`;
|
||||
const response = await this.http.get(path, true);
|
||||
const response = await this.http.get(path);
|
||||
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
|
@ -53,7 +53,6 @@ import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { validatePassword } from '@/utils/validation';
|
||||
@ -97,12 +96,11 @@ export default class DeleteAccountPopup extends Vue {
|
||||
try {
|
||||
await this.auth.delete(this.password);
|
||||
await this.$notify.success('Account was successfully deleted');
|
||||
|
||||
this.$segment.track(SegmentEvent.USER_DELETED, {
|
||||
email: this.$store.getters.user.email,
|
||||
});
|
||||
|
||||
AuthToken.remove();
|
||||
|
||||
this.isLoading = false;
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT);
|
||||
await this.$router.push(RouteConfig.Login.path);
|
||||
|
@ -30,7 +30,6 @@ import { RouteConfig } from '@/router';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import {
|
||||
API_KEYS_ACTIONS,
|
||||
APP_STATE_ACTIONS,
|
||||
@ -56,8 +55,6 @@ export default class AccountDropdown extends Vue {
|
||||
}
|
||||
|
||||
public onLogoutClick(): void {
|
||||
AuthToken.remove();
|
||||
|
||||
this.$router.push(RouteConfig.Login.path);
|
||||
this.$store.dispatch(PM_ACTIONS.CLEAR);
|
||||
this.$store.dispatch(PROJECTS_ACTIONS.CLEAR);
|
||||
|
@ -19,7 +19,6 @@ import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
|
||||
|
||||
import store from '@/store';
|
||||
import { NavigationLink } from '@/types/navigation';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
const DashboardArea = () => import('@/views/DashboardArea.vue');
|
||||
const ForgotPassword = () => import('@/views/forgotPassword/ForgotPassword.vue');
|
||||
const LoginArea = () => import('@/views/login/LoginArea.vue');
|
||||
@ -163,14 +162,6 @@ export const router = new Router({
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.some(route => route.meta.requiresAuth)) {
|
||||
if (!AuthToken.get()) {
|
||||
next(RouteConfig.Login.path);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (navigateToDefaultSubTab(to.matched, RouteConfig.Account)) {
|
||||
next(RouteConfig.Account.with(RouteConfig.Profile).path);
|
||||
|
||||
|
@ -6,23 +6,17 @@ import ApolloClient from 'apollo-client/ApolloClient';
|
||||
import { setContext } from 'apollo-link-context';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
|
||||
// Satellite url
|
||||
const satelliteUrl = new HttpLink({
|
||||
uri: process.env.VUE_APP_ENDPOINT_URL,
|
||||
});
|
||||
|
||||
// Adding auth headers
|
||||
// Adding additional headers
|
||||
const authLink = setContext((_, {headers}) => {
|
||||
// get the authentication token from local storage if it exists
|
||||
const token = AuthToken.get();
|
||||
|
||||
// return the headers to the context so httpLink can read them
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -1,39 +0,0 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// AuthToken exposes methods to manage auth cookie
|
||||
export class AuthToken {
|
||||
private static tokenKey: string = '_tokenKey';
|
||||
|
||||
public static get(): string {
|
||||
return AuthToken.getCookie(AuthToken.tokenKey);
|
||||
}
|
||||
|
||||
public static set(tokenValue: string): void {
|
||||
document.cookie = AuthToken.tokenKey + '=' + tokenValue + '; path=/';
|
||||
}
|
||||
|
||||
public static remove(): void {
|
||||
document.cookie = AuthToken.tokenKey + '=; path=/';
|
||||
}
|
||||
|
||||
private static getCookie(cname: string): string {
|
||||
const name: string = cname + '=';
|
||||
const decodedCookie: string = decodeURIComponent(document.cookie);
|
||||
const ca: string[] = decodedCookie.split(';');
|
||||
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
|
||||
/**
|
||||
* HttpClient is a custom wrapper around fetch api.
|
||||
* Exposes get, post and delete methods for JSON strings.
|
||||
@ -15,22 +13,16 @@ export class HttpClient {
|
||||
* @param body serialized JSON
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
private async sendJSON(method: string, path: string, body: string | null, auth: boolean): Promise<Response> {
|
||||
private async sendJSON(method: string, path: string, body: string | null): Promise<Response> {
|
||||
const request: RequestInit = {
|
||||
method: method,
|
||||
body: body,
|
||||
};
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
request.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (auth) {
|
||||
headers['Authorization'] = `Bearer ${AuthToken.get()}`;
|
||||
}
|
||||
|
||||
request.headers = headers;
|
||||
|
||||
return await fetch(path, request);
|
||||
}
|
||||
|
||||
@ -40,8 +32,8 @@ export class HttpClient {
|
||||
* @param body serialized JSON
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
public async post(path: string, body: string | null, auth: boolean = true): Promise<Response> {
|
||||
return this.sendJSON('POST', path, body, auth);
|
||||
public async post(path: string, body: string | null): Promise<Response> {
|
||||
return this.sendJSON('POST', path, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,8 +42,8 @@ export class HttpClient {
|
||||
* @param body serialized JSON
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
public async patch(path: string, body: string | null, auth: boolean = true): Promise<Response> {
|
||||
return this.sendJSON('PATCH', path, body, auth);
|
||||
public async patch(path: string, body: string | null): Promise<Response> {
|
||||
return this.sendJSON('PATCH', path, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,8 +52,8 @@ export class HttpClient {
|
||||
* @param body serialized JSON
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
public async put(path: string, body: string | null, auth: boolean = true): Promise<Response> {
|
||||
return this.sendJSON('PUT', path, body, auth);
|
||||
public async put(path: string, body: string | null): Promise<Response> {
|
||||
return this.sendJSON('PUT', path, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,8 +61,8 @@ export class HttpClient {
|
||||
* @param path
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
public async get(path: string, auth: boolean = true): Promise<Response> {
|
||||
return this.sendJSON('GET', path, null, auth);
|
||||
public async get(path: string): Promise<Response> {
|
||||
return this.sendJSON('GET', path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,7 +70,7 @@ export class HttpClient {
|
||||
* @param path
|
||||
* @param auth indicates if authentication is needed
|
||||
*/
|
||||
public async delete(path: string, auth: boolean = true): Promise<Response> {
|
||||
return this.sendJSON('DELETE', path, null, auth);
|
||||
public async delete(path: string): Promise<Response> {
|
||||
return this.sendJSON('DELETE', path, null);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { Project } from '@/types/projects';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import {
|
||||
API_KEYS_ACTIONS,
|
||||
APP_STATE_ACTIONS,
|
||||
@ -76,7 +75,6 @@ export default class DashboardArea extends Vue {
|
||||
} catch (error) {
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
|
||||
await this.$notify.error(error.message);
|
||||
AuthToken.remove();
|
||||
await this.$router.push(RouteConfig.Login.path);
|
||||
|
||||
return;
|
||||
@ -90,7 +88,6 @@ export default class DashboardArea extends Vue {
|
||||
await this.$store.dispatch(GET_PROJECT_CHARGES);
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorUnauthorized) {
|
||||
AuthToken.remove();
|
||||
await this.$router.push(RouteConfig.Login.path);
|
||||
|
||||
return;
|
||||
|
@ -14,7 +14,6 @@ import LoadingLogoIcon from '@/../static/images/LogoWhite.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { AppState } from '@/utils/constants/appStateEnum';
|
||||
@ -78,7 +77,6 @@ export default class Login extends Vue {
|
||||
|
||||
try {
|
||||
this.authToken = await this.auth.token(this.email, this.password);
|
||||
AuthToken.set(this.authToken);
|
||||
this.$segment.track(SegmentEvent.USER_LOGGED_IN, {
|
||||
email: this.email,
|
||||
});
|
||||
@ -96,7 +94,6 @@ export default class Login extends Vue {
|
||||
this.activateLoadingOverlay();
|
||||
|
||||
setTimeout(() => {
|
||||
AuthToken.set(this.authToken);
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
|
||||
this.isLoading = false;
|
||||
this.$router.push(RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).path);
|
||||
|
@ -14,7 +14,6 @@ import { makeProjectsModule } from '@/store/modules/projects';
|
||||
import { makeUsageModule } from '@/store/modules/usage';
|
||||
import { makeUsersModule } from '@/store/modules/users';
|
||||
import { User } from '@/types/users';
|
||||
import { AuthToken } from '@/utils/authToken';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { AppState } from '@/utils/constants/appStateEnum';
|
||||
import { NotificatorPlugin } from '@/utils/plugins/notificator';
|
||||
@ -98,8 +97,6 @@ describe('Dashboard', () => {
|
||||
});
|
||||
|
||||
it('loads routes correctly when authorithed without project with available routes', async () => {
|
||||
jest.spyOn(AuthToken, 'get').mockReturnValue('authToken');
|
||||
|
||||
const availableWithoutProject = [
|
||||
RouteConfig.Account.with(RouteConfig.Billing).path,
|
||||
RouteConfig.Account.with(RouteConfig.Profile).path,
|
||||
@ -119,8 +116,6 @@ describe('Dashboard', () => {
|
||||
});
|
||||
|
||||
it('loads routes correctly when authorithed without project with unavailable routes', async () => {
|
||||
jest.spyOn(AuthToken, 'get').mockReturnValue('authToken');
|
||||
|
||||
const unavailableWithoutProject = [
|
||||
RouteConfig.ApiKeys.path,
|
||||
RouteConfig.Buckets.path,
|
||||
|
Loading…
Reference in New Issue
Block a user