oauth fixes, login cookies fixes

pull/688/head
hunterlong 2020-06-25 17:15:59 -07:00
parent 7be131d0cf
commit 6f0f712bad
13 changed files with 157 additions and 75 deletions

View File

@ -9,7 +9,7 @@ const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.
class Api { class Api {
constructor() { constructor() {
axios.defaults.withCredentials = true
} }
async oauth() { async oauth() {
@ -251,17 +251,13 @@ class Api {
} }
token() { token() {
const tk = $cookies.get(tokenKey) return $cookies.get(tokenKey);
if (!tk) {
return {admin: false};
}
return tk;
} }
authToken() { authToken() {
const tk = $cookies.get(tokenKey) const tk = $cookies.get(tokenKey)
if (tk.token) { if (tk) {
return {'Authorization': 'Bearer ' + tk.token}; return {'Authorization': 'Bearer ' + tk};
} else { } else {
return {}; return {};
} }

View File

@ -52,7 +52,7 @@
this.$store.commit('setHasAllData', false) this.$store.commit('setHasAllData', false)
this.$store.commit('setToken', null) this.$store.commit('setToken', null)
this.$store.commit('setAdmin', false) this.$store.commit('setAdmin', false)
this.$cookies.remove("statping_auth") // this.$cookies.remove("statping_auth")
await this.$router.push('/logout') await this.$router.push('/logout')
} }
} }

View File

@ -80,9 +80,8 @@
if (auth.error) { if (auth.error) {
this.error = true this.error = true
} else if (auth.token) { } else if (auth.token) {
const u = {username: this.username, admin: auth.admin, token: auth.token} // this.$cookies.set("statping_auth", auth.token)
this.$cookies.set("statping_auth", JSON.stringify(u)) await this.$store.dispatch('loadAdmin')
this.$store.dispatch('loadAdmin')
this.$store.commit('setAdmin', auth.admin) this.$store.commit('setAdmin', auth.admin)
this.$router.push('/dashboard') this.$router.push('/dashboard')
} }

View File

@ -1,6 +1,5 @@
<template> <template>
<form @submit.prevent="saveOAuth"> <form @submit.prevent="saveOAuth">
{{core.oauth}}
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Internal Login</div> <div class="card-header">Internal Login</div>
<div class="card-body"> <div class="card-body">
@ -146,8 +145,8 @@
</div> </div>
</div> </div>
<button class="btn btn-primary btn-block" @click.prevent="saveOAuth" type="submit"> <button class="btn btn-primary btn-block" @click.prevent="saveOAuth" type="submit" :disabled="loading">
Save OAuth Settings <font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/> Save OAuth Settings
</button> </button>
</form> </form>
@ -162,9 +161,6 @@
core() { core() {
return this.$store.getters.core return this.$store.getters.core
}, },
auth() {
return this.$store.getters.oauth
}
}, },
data() { data() {
return { return {
@ -172,6 +168,7 @@
slack_enabled: false, slack_enabled: false,
github_enabled: false, github_enabled: false,
local_enabled: false, local_enabled: false,
loading: false,
oauth: { oauth: {
gh_client_id: "", gh_client_id: "",
gh_client_secret: "", gh_client_secret: "",
@ -185,8 +182,8 @@
} }
} }
}, },
mounted() { async mounted() {
this.oauth = this.auth this.oauth = await Api.oauth()
this.local_enabled = this.has('local') this.local_enabled = this.has('local')
this.github_enabled = this.has('github') this.github_enabled = this.has('github')
this.google_enabled = this.has('google') this.google_enabled = this.has('google')
@ -216,12 +213,12 @@
return this.oauth.oauth_providers.split(",").includes(val) return this.oauth.oauth_providers.split(",").includes(val)
}, },
async saveOAuth() { async saveOAuth() {
let c = this.core this.loading = true
c.oauth = this.oauth this.oauth.oauth_providers = this.providers()
c.oauth.oauth_providers = this.providers() await Api.oauth_save(this.oauth)
await Api.oauth_save(c)
const oauth = await Api.oauth() const oauth = await Api.oauth()
this.$store.commit('setOAuth', oauth) this.$store.commit('setOAuth', oauth)
this.loading = false
} }
} }
} }

View File

@ -38,7 +38,7 @@ const routes = [
beforeEnter: async (to, from, next) => { beforeEnter: async (to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) { if (to.matched.some(record => record.meta.requiresAuth)) {
let tk = await Api.token() let tk = await Api.token()
if (to.path !== '/login' && !tk.admin) { if (to.path !== '/login' && !tk) {
next('/login') next('/login')
return return
} }

View File

@ -147,9 +147,9 @@ export default new Vuex.Store({
const core = await Api.core() const core = await Api.core()
const token = await Api.token() const token = await Api.token()
context.commit("setCore", core); context.commit("setCore", core);
context.commit('setAdmin', token.admin) context.commit('setAdmin', token)
context.commit('setCore', core) context.commit('setCore', core)
context.commit('setUser', token.token!==undefined) context.commit('setUser', token!==undefined)
}, },
async loadRequired(context) { async loadRequired(context) {
const groups = await Api.groups() const groups = await Api.groups()
@ -170,14 +170,8 @@ export default new Vuex.Store({
const messages = await Api.messages() const messages = await Api.messages()
context.commit("setMessages", messages) context.commit("setMessages", messages)
context.commit("setHasPublicData", true) context.commit("setHasPublicData", true)
try {
const checkins = await Api.checkins() const checkins = await Api.checkins()
context.commit("setCheckins", checkins); context.commit("setCheckins", checkins);
} catch(e) {
window.console.error(e)
await Api.logout()
return
}
const notifiers = await Api.notifiers() const notifiers = await Api.notifiers()
context.commit("setNotifiers", notifiers); context.commit("setNotifiers", notifiers);
const users = await Api.users() const users = await Api.users()

View File

@ -3,14 +3,23 @@ module.exports = {
assetsDir: 'assets', assetsDir: 'assets',
filenameHashing: false, filenameHashing: false,
devServer: { devServer: {
proxy: { disableHostCheck: true,
proxyTable: {
'/api': { '/api': {
logLevel: 'debug', logLevel: 'debug',
target: 'http://0.0.0.0:8585' target: 'http://0.0.0.0:8585',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}, },
'/oauth': { '/oauth': {
logLevel: 'debug', logLevel: 'debug',
target: 'http://0.0.0.0:8585/oauth/' target: 'http://0.0.0.0:8585',
changeOrigin: true,
pathRewrite: {
'^/oauth': ''
}
} }
} }
} }

View File

@ -46,13 +46,20 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
func apiUpdateOAuthHandler(w http.ResponseWriter, r *http.Request) { func apiUpdateOAuthHandler(w http.ResponseWriter, r *http.Request) {
var c core.OAuth var c core.OAuth
err := DecodeJSON(r, &c) if err := DecodeJSON(r, &c); err != nil {
if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
app := core.App app := core.App
app.OAuth = c app.OAuth = c
if err := app.Update(); err != nil {
sendErrorJson(err, w, r)
return
}
fmt.Println(app)
sendJsonAction(app.OAuth, "update", w, r) sendJsonAction(app.OAuth, "update", w, r)
} }

View File

@ -19,7 +19,7 @@ const (
) )
var ( var (
jwtKey string jwtKey []byte
httpServer *http.Server httpServer *http.Server
usingSSL bool usingSSL bool
mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}` mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
@ -96,7 +96,11 @@ func IsFullAuthenticated(r *http.Request) bool {
if ok := hasAuthorizationHeader(r); ok { if ok := hasAuthorizationHeader(r); ok {
return true return true
} }
return IsAdmin(r) claim, err := getJwtToken(r)
if err != nil {
return false
}
return claim.Admin
} }
// ScopeName will show private JSON fields in the API. // ScopeName will show private JSON fields in the API.

View File

@ -33,7 +33,7 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
ExpiresAt: expirationTime.Unix(), ExpiresAt: expirationTime.Unix(),
}} }}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaim) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaim)
tokenString, err := token.SignedString([]byte(jwtKey)) tokenString, err := token.SignedString(jwtKey)
if err != nil { if err != nil {
log.Errorln("error setting token: ", err) log.Errorln("error setting token: ", err)
} }
@ -44,6 +44,7 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
Value: tokenString, Value: tokenString,
Expires: expirationTime, Expires: expirationTime,
MaxAge: int(time.Duration(72 * time.Hour).Seconds()), MaxAge: int(time.Duration(72 * time.Hour).Seconds()),
Path: "/",
}) })
return jwtClaim, tokenString return jwtClaim, tokenString
} }
@ -56,11 +57,12 @@ func getJwtToken(r *http.Request) (JwtClaim, error) {
} }
return JwtClaim{}, err return JwtClaim{}, err
} }
tknStr := c.Value
var claims JwtClaim var claims JwtClaim
tkn, err := jwt.ParseWithClaims(tknStr, &claims, func(token *jwt.Token) (interface{}, error) { tkn, err := jwt.ParseWithClaims(c.Value, &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtKey), nil return jwtKey, nil
}) })
if err != nil { if err != nil {
if err == jwt.ErrSignatureInvalid { if err == jwt.ErrSignatureInvalid {
return JwtClaim{}, err return JwtClaim{}, err

View File

@ -9,7 +9,6 @@ import (
"github.com/statping/statping/types/users" "github.com/statping/statping/types/users"
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
"golang.org/x/oauth2/slack" "golang.org/x/oauth2/slack"
"net/http" "net/http"
@ -51,40 +50,18 @@ func oauthHandler(w http.ResponseWriter, r *http.Request) {
} }
func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) { func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) {
log.Infoln(oauth)
user := &users.User{ user := &users.User{
Id: 0, Id: 0,
Username: oauth.Username, Username: oauth.Username,
Email: oauth.Email, Email: oauth.Email,
Admin: null.NewNullBool(true), Admin: null.NewNullBool(true),
} }
log.Infoln(fmt.Sprintf("OAuth User %s logged in from IP %s", oauth.Email, r.RemoteAddr)) log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type, oauth.Email, r.RemoteAddr))
setJwtToken(user, w) setJwtToken(user, w)
//returnJson(user, w, r) //returnJson(user, w, r)
http.Redirect(w, r, core.App.Domain, http.StatusPermanentRedirect) http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect)
}
func githubOAuth(r *http.Request) (*oAuth, error) {
c := *core.App
code := r.URL.Query().Get("code")
config := &oauth2.Config{
ClientID: c.OAuth.GithubClientID,
ClientSecret: c.OAuth.GithubClientSecret,
Endpoint: github.Endpoint,
}
gg, err := config.Exchange(r.Context(), code)
if err != nil {
return nil, err
}
return &oAuth{
Token: gg.AccessToken,
RefreshToken: gg.RefreshToken,
Valid: gg.Valid(),
}, nil
} }
func googleOAuth(r *http.Request) (*oAuth, error) { func googleOAuth(r *http.Request) (*oAuth, error) {

99
handlers/oauth_github.go Normal file
View File

@ -0,0 +1,99 @@
package handlers
import (
"encoding/json"
"github.com/statping/statping/types/core"
"github.com/statping/statping/utils"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"net/http"
"strings"
"time"
)
func githubOAuth(r *http.Request) (*oAuth, error) {
c := *core.App
code := r.URL.Query().Get("code")
config := &oauth2.Config{
ClientID: c.OAuth.GithubClientID,
ClientSecret: c.OAuth.GithubClientSecret,
Endpoint: github.Endpoint,
}
gg, err := config.Exchange(r.Context(), code)
if err != nil {
return nil, err
}
headers := []string{
"Accept=application/vnd.github.machine-man-preview+json",
"Authorization=token " + gg.AccessToken,
}
resp, _, err := utils.HttpRequest("https://api.github.com/user", "GET", nil, headers, nil, 10*time.Second, true, nil)
if err != nil {
return nil, err
}
var user githubUser
if err := json.Unmarshal(resp, &user); err != nil {
return nil, err
}
return &oAuth{
Token: gg.AccessToken,
RefreshToken: gg.RefreshToken,
Valid: gg.Valid(),
Username: strings.ToLower(user.Name),
Email: strings.ToLower(user.Email),
Type: "github",
}, nil
}
type githubUser struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
Blog string `json:"blog"`
Location string `json:"location"`
Email string `json:"email"`
Hireable bool `json:"hireable"`
Bio string `json:"bio"`
TwitterUsername string `json:"twitter_username"`
PublicRepos int `json:"public_repos"`
PublicGists int `json:"public_gists"`
Followers int `json:"followers"`
Following int `json:"following"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrivateGists int `json:"private_gists"`
TotalPrivateRepos int `json:"total_private_repos"`
OwnedPrivateRepos int `json:"owned_private_repos"`
DiskUsage int `json:"disk_usage"`
Collaborators int `json:"collaborators"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Plan struct {
Name string `json:"name"`
Space int `json:"space"`
PrivateRepos int `json:"private_repos"`
Collaborators int `json:"collaborators"`
} `json:"plan"`
}

View File

@ -1,12 +1,10 @@
package handlers package handlers
import ( import (
"fmt"
sentryhttp "github.com/getsentry/sentry-go/http" sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/statping/statping/source" "github.com/statping/statping/source"
"github.com/statping/statping/types/core"
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
@ -192,5 +190,5 @@ func resetRouter() {
} }
func resetCookies() { func resetCookies() {
jwtKey = fmt.Sprintf("%s_%d", core.App.ApiSecret, utils.Now().Nanosecond()) jwtKey = []byte(utils.NewSHA256Hash())
} }