Merge pull request #564 from statping/auth-fixes-1

Authentication Fixes, Build updates
pull/576/head
Hunter Long 2020-05-04 22:27:06 -07:00 committed by GitHub
commit 8e76b87348
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 185 additions and 101 deletions

View File

@ -160,7 +160,7 @@ build-linux:
do \
echo "Building v$$VERSION for $$os-$$arch"; \
mkdir -p releases/statping-$$os-$$arch/; \
GO111MODULE="on" GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd || true; \
CGO_ENABLED=1 GO111MODULE="on" GOOS=$$os GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION}" -o releases/statping-$$os-$$arch/statping ${PWD}/cmd || true; \
chmod +x releases/statping-$$os-$$arch/statping || true; \
tar -czf releases/statping-$$os-$$arch.tar.gz -C releases/statping-$$os-$$arch statping || true; \
done \
@ -174,7 +174,7 @@ build-mac:
do \
echo "Building v$$VERSION for darwin-$$arch"; \
mkdir -p releases/statping-darwin-$$arch/; \
GO111MODULE="on" GOOS=darwin GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-darwin-$$arch/statping ${PWD}/cmd || true; \
CGO_ENABLED=1 GO111MODULE="on" GOOS=darwin GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION}" -o releases/statping-darwin-$$arch/statping ${PWD}/cmd || true; \
chmod +x releases/statping-darwin-$$arch/statping || true; \
tar -czf releases/statping-darwin-$$arch.tar.gz -C releases/statping-darwin-$$arch statping || true; \
done
@ -187,7 +187,7 @@ build-win:
do \
echo "Building v$$VERSION for windows-$$arch"; \
mkdir -p releases/statping-windows-$$arch/; \
GO111MODULE="on" GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd || true; \
CGO_ENABLED=1 GO111MODULE="on" GOOS=windows GOARCH=$$arch go build -a -ldflags "-X main.VERSION=${VERSION}" -o releases/statping-windows-$$arch/statping.exe ${PWD}/cmd || true; \
chmod +x releases/statping-windows-$$arch/statping.exe || true; \
zip -j releases/statping-windows-$$arch.zip releases/statping-windows-$$arch/statping.exe || true; \
done

View File

@ -33,6 +33,7 @@
"vue-apexcharts": "^1.5.2",
"vue-clipboard2": "^0.3.1",
"vue-codemirror": "^4.0.6",
"vue-cookies": "^1.7.0",
"vue-flatpickr-component": "^8.1.5",
"vue-github-button": "^1.1.2",
"vue-moment": "^4.1.0",

View File

@ -4,7 +4,7 @@ import * as Sentry from "@sentry/browser";
import * as Integrations from "@sentry/integrations";
const qs = require('querystring');
const tokenKey = "statping_user";
const tokenKey = "statping_auth";
const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.com/3"
class Api {
@ -29,6 +29,10 @@ class Api {
return axios.post('api/core', obj).then(response => (response.data))
}
async oauth_save(obj) {
return axios.post('api/oauth', obj).then(response => (response.data))
}
async setup_save(data) {
return axios.post('api/setup', qs.stringify(data)).then(response => (response.data))
}
@ -228,19 +232,11 @@ class Api {
async login(username, password) {
const f = {username: username, password: password}
return axios.post('api/login', qs.stringify(f))
.then(response => (response.data))
return axios.post('api/login', qs.stringify(f)).then(response => (response.data))
}
async logout() {
await axios.get('api/logout').then(response => (response.data))
return localStorage.removeItem(tokenKey)
}
saveToken(username, token, admin) {
const user = {username: username, token: token, admin: admin}
localStorage.setItem(tokenKey, JSON.stringify(user));
return user
return axios.get('api/logout').then(response => (response.data))
}
async scss_base() {
@ -255,17 +251,17 @@ class Api {
}
token() {
const tk = localStorage.getItem(tokenKey)
const tk = $cookies.get(tokenKey)
if (!tk) {
return {};
return {admin: false};
}
return JSON.parse(tk);
return tk;
}
authToken() {
let user = JSON.parse(localStorage.getItem(tokenKey));
if (user && user.token) {
return {'Authorization': 'Bearer ' + user.token};
const tk = $cookies.get(tokenKey)
if (tk.token) {
return {'Authorization': 'Bearer ' + tk.token};
} else {
return {};
}

View File

@ -32,22 +32,19 @@
this.$router.push('/setup')
}
if (this.$route.path !== '/setup') {
if (this.core.logged_in) {
if (this.$store.state.admin) {
await this.$store.dispatch('loadAdmin')
} else {
await this.$store.dispatch('loadRequired')
}
this.loaded = true
}
},
async mounted() {
if (this.$route.path !== '/setup') {
const tk = localStorage.getItem("statping_user")
if (this.core.logged_in) {
if (this.$store.state.admin) {
this.logged_in = true
await this.$store.dispatch('loadAdmin')
// await this.$store.dispatch('loadAdmin')
}
}
}

View File

@ -28,7 +28,7 @@
</li>
</ul>
<span class="navbar-text">
<a href="#" class="nav-link" v-on:click="logout">Logout</a>
<a href="#" class="nav-link" @click.prevent="logout">Logout</a>
</span>
</div>
</nav>
@ -37,6 +37,7 @@
<script>
import Api from "../../API"
import Vue from "vue";
export default {
name: 'TopNav',
@ -51,6 +52,7 @@
this.$store.commit('setHasAllData', false)
this.$store.commit('setToken', null)
this.$store.commit('setAdmin', false)
this.$cookies.remove("statping_auth")
await this.$router.push('/logout')
}
}

View File

@ -4,7 +4,7 @@
<a href="https://github.com/statping/statping" target="_blank">
Statping {{core.version}} made with <font-awesome-icon icon="heart"/>
</a> |
<router-link :to="core.logged_in ? '/dashboard' : '/login'">Dashboard</router-link>
<router-link :to="$store.state.admin ? '/dashboard' : '/login'">Dashboard</router-link>
</div>
<div v-else class="footer text-center mb-4 p-2" v-html="core.footer"></div>
</footer>

View File

@ -25,16 +25,16 @@
</div>
</form>
<a v-if="oauth.gh_client_id" :href="GHlogin()" class="btn btn-block">
Github Login
<a v-if="oauth.gh_client_id" @click.prevent="GHlogin" href="#" class="btn btn-block btn-outline-dark">
<font-awesome-icon :icon="['fab', 'github']" /> Login with Github
</a>
<a v-if="oauth.slack_client_id" :href="Slacklogin()" class="btn btn-block">
Slack Login
<a v-if="oauth.slack_client_id" @click.prevent="Slacklogin" href="#" class="btn btn-block btn-outline-dark">
<font-awesome-icon :icon="['fab', 'slack']" /> Login with Slack
</a>
<a v-if="oauth.google_client_id" :href="Googlelogin()" class="btn btn-block">
Google Login
<a v-if="oauth.google_client_id" @click.prevent="Googlelogin" href="#" class="btn btn-block btn-outline-dark">
<font-awesome-icon :icon="['fab', 'google']" /> Login with Google
</a>
</div>
@ -80,7 +80,8 @@
if (auth.error) {
this.error = true
} else if (auth.token) {
this.auth = Api.saveToken(this.username, auth.token, auth.admin)
const u = {username: this.username, admin: auth.admin, token: auth.token}
this.$cookies.set("statping_auth", JSON.stringify(u))
this.$store.dispatch('loadAdmin')
this.$store.commit('setAdmin', auth.admin)
this.$router.push('/dashboard')
@ -88,13 +89,13 @@
this.loading = false
},
GHlogin() {
return `https://github.com/login/oauth/authorize?client_id=${this.oauth.gh_client_id}&redirect_uri=${this.core.domain}/api/oauth/github&scope=user,repo`
window.location = `https://github.com/login/oauth/authorize?client_id=${this.oauth.gh_client_id}&redirect_uri=${this.core.domain}/oauth/github&scope=user,repo`
},
Slacklogin() {
return `https://slack.com/oauth/authorize?client_id=${this.oauth.slack_client_id}&redirect_uri=${this.core.domain}/api/oauth/slack&scope=users.profile:read,users:read.email`
window.location = `https://slack.com/oauth/authorize?client_id=${this.oauth.slack_client_id}&redirect_uri=${this.core.domain}/oauth/slack&scope=identity.basic`
},
Googlelogin() {
return `https://accounts.google.com/signin/oauth?client_id=${this.oauth.google_client_id}&redirect_uri=${this.core.domain}/api/oauth/google&response_type=code&scope=https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email`
window.location = `https://accounts.google.com/signin/oauth?client_id=${this.oauth.google_client_id}&redirect_uri=${this.core.domain}/oauth/google&response_type=code&scope=https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email`
}
}
}

View File

@ -162,7 +162,7 @@
core() {
return this.$store.getters.core
},
oauth() {
auth() {
return this.$store.getters.oauth
}
},
@ -171,10 +171,22 @@
google_enabled: false,
slack_enabled: false,
github_enabled: false,
local_enabled: false
local_enabled: false,
oauth: {
gh_client_id: "",
gh_client_secret: "",
google_client_id: "",
google_client_secret: "",
oauth_domains: "",
oauth_providers: "",
slack_client_id: "",
slack_client_secret: "",
slack_team: ""
}
}
},
mounted() {
this.oauth = this.auth
this.local_enabled = this.has('local')
this.github_enabled = this.has('github')
this.google_enabled = this.has('google')
@ -207,10 +219,9 @@
let c = this.core
c.oauth = this.oauth
c.oauth.oauth_providers = this.providers()
await Api.core_save(c)
const core = await Api.core()
this.$store.commit('setCore', core)
this.$store.commit('setOAuth', c.oauth)
await Api.oauth_save(c)
const oauth = await Api.oauth()
this.$store.commit('setOAuth', oauth)
}
}
}

View File

@ -3,19 +3,23 @@ import VueRouter from 'vue-router'
import VueApexCharts from 'vue-apexcharts'
import VueObserveVisibility from 'vue-observe-visibility'
import VueClipboard from 'vue-clipboard2'
import VueCookies from 'vue-cookies'
import App from '@/App.vue'
import store from './store'
import router from './routes'
import "./mixin"
import "./icons"
Vue.component('apexchart', VueApexCharts)
Vue.use(VueClipboard);
Vue.use(VueRouter);
Vue.use(VueObserveVisibility);
Vue.use(VueCookies);
Vue.$cookies.config('3d')
import router from './routes'
import "./mixin"
import "./icons"
Vue.config.productionTip = false
new Vue({

View File

@ -34,7 +34,18 @@ const routes = [
meta: {
requiresAuth: true
},
beforeEnter: CheckAuth,
beforeEnter: async (to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
let tk = await Api.token()
if (to.path !== '/login' && !tk.admin) {
next('/login')
return
}
next()
} else {
next()
}
},
children: [{
path: '',
component: DashboardIndex,
@ -135,9 +146,10 @@ const router = new VueRouter({
routes
})
function CheckAuth(to, from, next) {
let CheckAuth = (to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
let item = localStorage.getItem("statping_user")
let item = this.$cookies.get("statping_auth")
window.console.log(item)
if (to.path !== '/login' && !item) {
next('/login')
return

View File

@ -102,7 +102,6 @@ export default new Vuex.Store({
state.hasPublicData = bool
},
setCore (state, core) {
window.console.log('GETTING CORE')
state.core = core
},
setToken (state, token) {
@ -146,10 +145,11 @@ export default new Vuex.Store({
},
async loadCore(context) {
const core = await Api.core()
const token = await Api.token()
context.commit("setCore", core);
context.commit('setAdmin', core.admin)
context.commit('setAdmin', token.admin)
context.commit('setCore', core)
context.commit('setUser', core.logged_in)
context.commit('setUser', token.token!==undefined)
},
async loadRequired(context) {
const groups = await Api.groups()

View File

@ -10,7 +10,7 @@ module.exports = {
},
'/oauth': {
logLevel: 'debug',
target: 'http://0.0.0.0:8585'
target: 'http://0.0.0.0:8585/oauth/'
}
}
}

View File

@ -11838,6 +11838,11 @@ vue-codemirror@^4.0.6:
codemirror "^5.41.0"
diff-match-patch "^1.0.0"
vue-cookies@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/vue-cookies/-/vue-cookies-1.7.0.tgz#dd8f147ff1579e0cbd5f352dc5dff2696d8760b8"
integrity sha512-vuEUm6wYMMrFAHFCrkzIUAy8+MgPAbBGmYXnk2M6X6O2KHbMT1wuDD2izacmsSUp6ZM02e23MJRtPRobl88VMg==
vue-eslint-parser@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61"

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.3.5
github.com/gorilla/mux v1.7.4
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
github.com/jinzhu/gorm v1.9.12
github.com/joho/godotenv v1.3.0

View File

@ -27,13 +27,7 @@ type apiResponse struct {
}
func apiIndexHandler(r *http.Request) interface{} {
coreClone := *core.App
_, err := getJwtToken(r)
if err == nil {
coreClone.LoggedIn = true
coreClone.IsAdmin = IsAdmin(r)
}
return coreClone
return core.App
}
func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
@ -50,6 +44,18 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
returnJson(output, w, r)
}
func apiUpdateOAuthHandler(w http.ResponseWriter, r *http.Request) {
var c core.OAuth
err := DecodeJSON(r, &c)
if err != nil {
sendErrorJson(err, w, r)
return
}
app := core.App
app.OAuth = c
sendJsonAction(app.OAuth, "update", w, r)
}
func apiOAuthHandler(r *http.Request) interface{} {
app := core.App
return app.OAuth
@ -78,7 +84,6 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
if c.Domain != app.Domain {
app.Domain = c.Domain
}
app.OAuth = c.OAuth
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
app.AllowReports = null.NewNullBool(c.AllowReports.Bool)
utils.SentryInit(nil, app.AllowReports.Bool)

View File

@ -15,7 +15,9 @@ import (
func logoutHandler(w http.ResponseWriter, r *http.Request) {
removeJwtToken(w)
http.Redirect(w, r, basePath, http.StatusSeeOther)
out := make(map[string]string)
out["status"] = "success"
returnJson(out, w, r)
}
func logsHandler(w http.ResponseWriter, r *http.Request) {
@ -163,7 +165,7 @@ func removeJwtToken(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: cookieKey,
Value: "",
Expires: utils.Now(),
Expires: time.Now(),
})
}
@ -178,8 +180,10 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaim)
tokenString, err := token.SignedString([]byte(jwtKey))
if err != nil {
utils.Log.Errorln("error setting token: ", err)
log.Errorln("error setting token: ", err)
}
user.Token = tokenString
// set cookies
http.SetCookie(w, &http.Cookie{
Name: cookieKey,
Value: tokenString,
@ -195,9 +199,8 @@ func apiLoginHandler(w http.ResponseWriter, r *http.Request) {
user, auth := users.AuthUser(username, password)
if auth {
utils.Log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
claim, token := setJwtToken(user, w)
resp := struct {
Token string `json:"token"`
IsAdmin bool `json:"admin"`

View File

@ -1,41 +1,48 @@
package handlers
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/users"
"github.com/statping/statping/utils"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/slack"
"net/http"
"time"
)
var oauthSession = sessions.NewCookieStore([]byte("statping_oauth"))
type oAuth struct {
ID string
Email string
Username string
Token string
RefreshToken string
Valid bool
Type string
}
func oauthHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
provider := vars["provider"]
code := r.URL.Query().Get("code")
fmt.Println("code: ", code)
fmt.Println("client: ", core.App.OAuth.SlackClientID)
fmt.Println("secret: ", core.App.OAuth.SlackClientSecret)
var err error
var oauth *oAuth
switch provider {
case "google":
err, oauth = googleOAuth(r)
oauth, err = googleOAuth(r)
case "github":
err, oauth = githubOAuth(r)
oauth, err = githubOAuth(r)
case "slack":
err, oauth = slackOAuth(r)
oauth, err = slackOAuth(r)
}
if err != nil {
@ -51,17 +58,18 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) {
log.Infoln(oauth)
user := &users.User{
Id: 0,
Username: oauth.Email,
Username: oauth.Username,
Email: oauth.Email,
Admin: null.NewNullBool(true),
}
log.Infoln(fmt.Sprintf("OAuth User %v logged in from IP %v", oauth.Email, r.RemoteAddr))
log.Infoln(fmt.Sprintf("OAuth User %s logged in from IP %s", oauth.Email, r.RemoteAddr))
setJwtToken(user, w)
http.Redirect(w, r, basePath+"dashboard", http.StatusSeeOther)
//returnJson(user, w, r)
http.Redirect(w, r, core.App.Domain, http.StatusPermanentRedirect)
}
func githubOAuth(r *http.Request) (error, *oAuth) {
func githubOAuth(r *http.Request) (*oAuth, error) {
c := *core.App
code := r.URL.Query().Get("code")
@ -73,58 +81,94 @@ func githubOAuth(r *http.Request) (error, *oAuth) {
gg, err := config.Exchange(r.Context(), code)
if err != nil {
return err, nil
return nil, err
}
return nil, &oAuth{
return &oAuth{
Token: gg.AccessToken,
RefreshToken: gg.RefreshToken,
Valid: gg.Valid(),
}
}, nil
}
func googleOAuth(r *http.Request) (error, *oAuth) {
func googleOAuth(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,
ClientID: c.OAuth.GoogleClientID,
ClientSecret: c.OAuth.GoogleClientSecret,
Endpoint: google.Endpoint,
}
gg, err := config.Exchange(r.Context(), code)
if err != nil {
return err, nil
return nil, err
}
return nil, &oAuth{
return &oAuth{
Token: gg.AccessToken,
RefreshToken: gg.RefreshToken,
Valid: gg.Valid(),
}
}, nil
}
func slackOAuth(r *http.Request) (error, *oAuth) {
c := *core.App
func slackOAuth(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,
ClientID: c.OAuth.SlackClientID,
ClientSecret: c.OAuth.SlackClientSecret,
Endpoint: slack.Endpoint,
RedirectURL: c.Domain + basePath + "oauth/slack",
Scopes: []string{"identity.basic"},
}
gg, err := config.Exchange(r.Context(), code)
if err != nil {
return err, nil
return nil, err
}
return nil, &oAuth{
oauther := &oAuth{
Token: gg.AccessToken,
RefreshToken: gg.RefreshToken,
Valid: gg.Valid(),
Type: gg.Type(),
}
return oauther.slackIdentity()
}
// slackIdentity will query the Slack API to fetch the users ID, username, and email address.
func (a *oAuth) slackIdentity() (*oAuth, error) {
url := fmt.Sprintf("https://slack.com/api/users.identity?token=%s", a.Token)
out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true)
if err != nil {
return a, err
}
defer resp.Body.Close()
var i *slackIdentity
if err := json.Unmarshal(out, &i); err != nil {
return a, err
}
a.Email = i.User.Email
a.ID = i.User.ID
a.Username = i.User.Name
return a, nil
}
type slackIdentity struct {
Ok bool `json:"ok"`
User struct {
Name string `json:"name"`
ID string `json:"id"`
Email string `json:"email"`
} `json:"user"`
Team struct {
ID string `json:"id"`
} `json:"team"`
}
func secureToken(w http.ResponseWriter, r *http.Request) {

View File

@ -24,6 +24,7 @@ func staticAssets(src string) http.Handler {
func Router() *mux.Router {
dir := utils.Directory
CacheStorage = NewStorage()
r := mux.NewRouter().StrictSlash(true)
authUser := utils.Params.GetString("AUTH_USERNAME")
@ -74,16 +75,19 @@ func Router() *mux.Router {
r.Handle("/api", scoped(apiIndexHandler))
r.Handle("/api/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
api.Handle("/api/login", http.HandlerFunc(apiLoginHandler)).Methods("POST")
api.Handle("/api/logout", http.HandlerFunc(logoutHandler))
api.Handle("/api/logout", authenticated(logoutHandler, false))
api.Handle("/api/renew", authenticated(apiRenewHandler, false))
api.Handle("/api/cache", authenticated(apiCacheHandler, false)).Methods("GET")
api.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
api.Handle("/api/core", authenticated(apiCoreHandler, false)).Methods("POST")
api.Handle("/api/oauth", scoped(apiOAuthHandler)).Methods("GET")
api.Handle("/oauth/{provider}", http.HandlerFunc(oauthHandler))
api.Handle("/api/logs", authenticated(logsHandler, false)).Methods("GET")
api.Handle("/api/logs/last", authenticated(logsLineHandler, false)).Methods("GET")
// API OAUTH Routes
api.Handle("/api/oauth", scoped(apiOAuthHandler)).Methods("GET")
api.Handle("/api/oauth", authenticated(apiUpdateOAuthHandler, false)).Methods("POST")
api.Handle("/oauth/{provider}", http.HandlerFunc(oauthHandler))
// API SCSS and ASSETS Routes
api.Handle("/api/theme", authenticated(apiThemeViewHandler, false)).Methods("GET")
api.Handle("/api/theme", authenticated(apiThemeSaveHandler, false)).Methods("POST")

View File

@ -148,9 +148,8 @@ func TestApiUsersRoutes(t *testing.T) {
Name: "Statping Logout",
URL: "/api/logout",
Method: "GET",
ExpectedStatus: 303,
},
{
ExpectedStatus: 200,
}, {
Name: "Incorrect JSON POST",
URL: "/api/users",
Body: BadJSON,

View File

@ -31,8 +31,6 @@ type Core struct {
Setup bool `gorm:"-" json:"setup"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn null.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
LoggedIn bool `gorm:"-" json:"logged_in"`
IsAdmin bool `gorm:"-" json:"admin"`
AllowReports null.NullBool `gorm:"column:allow_reports;default:false" json:"allow_reports,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`

View File

@ -16,4 +16,5 @@ type User struct {
Admin null.NullBool `gorm:"column:administrator" json:"admin,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Token string `gorm:"-" json:"token"`
}