diff --git a/frontend/package.json b/frontend/package.json index f6bd23ee..fd16c2d5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/API.js b/frontend/src/API.js index 0ae562e2..e062a728 100644 --- a/frontend/src/API.js +++ b/frontend/src/API.js @@ -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 {}; } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c605d153..323a7651 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -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') } } } diff --git a/frontend/src/components/Dashboard/TopNav.vue b/frontend/src/components/Dashboard/TopNav.vue index b87c360c..3bfa458a 100644 --- a/frontend/src/components/Dashboard/TopNav.vue +++ b/frontend/src/components/Dashboard/TopNav.vue @@ -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') } } diff --git a/frontend/src/components/Index/Footer.vue b/frontend/src/components/Index/Footer.vue index eb00da20..8083d947 100644 --- a/frontend/src/components/Index/Footer.vue +++ b/frontend/src/components/Index/Footer.vue @@ -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> diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index 7b02ba47..412ca244 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -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` } } } diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index bb29c725..99e63ccd 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -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) } } } diff --git a/frontend/src/main.js b/frontend/src/main.js index 2cdfda83..db97a220 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -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({ diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 4cdfd799..bb6a1176 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -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 diff --git a/frontend/src/store.js b/frontend/src/store.js index 5d7360b5..197173b6 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -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() diff --git a/frontend/vue.config.js b/frontend/vue.config.js index edcf4565..8ce147cc 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -10,7 +10,7 @@ module.exports = { }, '/oauth': { logLevel: 'debug', - target: 'http://0.0.0.0:8585' + target: 'http://0.0.0.0:8585/oauth/' } } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5661e487..8d107e34 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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" diff --git a/go.mod b/go.mod index f7a30637..d256b14e 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/handlers/api.go b/handlers/api.go index d07805d8..e04d1777 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -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) diff --git a/handlers/dashboard.go b/handlers/dashboard.go index 5a59b53a..2aa9dc71 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -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"` diff --git a/handlers/oauth.go b/handlers/oauth.go index e495cf66..09ceb8d0 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -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) { diff --git a/handlers/routes.go b/handlers/routes.go index 291a584d..2db26c1e 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -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") diff --git a/types/core/struct.go b/types/core/struct.go index 75ad7106..6f02e14f 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -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"` diff --git a/types/users/struct.go b/types/users/struct.go index 8d1b790f..78f4b423 100644 --- a/types/users/struct.go +++ b/types/users/struct.go @@ -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"` }