Adding multi-session cookie
Этот коммит содержится в:
@@ -30,12 +30,12 @@ type Context struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
TemplateName string
|
TemplateName string
|
||||||
Props map[string]string
|
Props map[string]string
|
||||||
ClientCfg map[string]string
|
ClientCfg map[string]string
|
||||||
User *model.User
|
User *model.User
|
||||||
Team *model.Team
|
Team *model.Team
|
||||||
Session *model.Session
|
SessionTokenHash string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
@@ -99,8 +99,29 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Attempt to parse the token from the cookie
|
// Attempt to parse the token from the cookie
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
if cookie, err := r.Cookie(model.SESSION_TOKEN); err == nil {
|
if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
token = cookie.Value
|
multiToken := cookie.Value
|
||||||
|
|
||||||
|
fmt.Println(">>>>>>>> multiToken: " + multiToken)
|
||||||
|
|
||||||
|
if len(multiToken) > 0 {
|
||||||
|
tokens := strings.Split(multiToken, " ")
|
||||||
|
|
||||||
|
// If there is only 1 token in the cookie then just use it like normal
|
||||||
|
if len(tokens) == 1 {
|
||||||
|
token = multiToken
|
||||||
|
} else {
|
||||||
|
// If it is a multi-session token then find the correct session
|
||||||
|
sessionTokenHash := r.Header.Get(model.HEADER_MM_SESSION_TOKEN_HASH)
|
||||||
|
fmt.Println(">>>>>>>> sessionHash: " + sessionTokenHash + " url=" + r.URL.Path)
|
||||||
|
for _, t := range tokens {
|
||||||
|
if sessionTokenHash == model.HashPassword(t) {
|
||||||
|
token = token
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +200,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(c.Err.ToJson()))
|
w.Write([]byte(c.Err.ToJson()))
|
||||||
} else {
|
} else {
|
||||||
if c.Err.StatusCode == http.StatusUnauthorized {
|
if c.Err.StatusCode == http.StatusUnauthorized {
|
||||||
|
fmt.Println("!!!!!!!!!!!!!!!! url=" + r.URL.Path)
|
||||||
http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
||||||
} else {
|
} else {
|
||||||
RenderWebError(c.Err, w, r)
|
RenderWebError(c.Err, w, r)
|
||||||
@@ -310,25 +332,13 @@ func (c *Context) IsTeamAdmin() bool {
|
|||||||
|
|
||||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
sessionCache.Remove(c.Session.Token)
|
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: model.SESSION_TOKEN,
|
|
||||||
Value: "",
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: -1,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
|
|
||||||
multiToken := ""
|
multiToken := ""
|
||||||
if oldMultiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
if oldMultiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
multiToken = oldMultiCookie.Value
|
multiToken = oldMultiCookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
multiCookie := &http.Cookie{
|
multiCookie := &http.Cookie{
|
||||||
Name: model.MULTI_SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
|
Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
||||||
@@ -500,7 +510,7 @@ func GetSession(token string) *model.Session {
|
|||||||
|
|
||||||
func FindMultiSessionForTeamId(r *http.Request, teamId string) *model.Session {
|
func FindMultiSessionForTeamId(r *http.Request, teamId string) *model.Session {
|
||||||
|
|
||||||
if multiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
if multiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
multiToken := multiCookie.Value
|
multiToken := multiCookie.Value
|
||||||
|
|
||||||
if len(multiToken) > 0 {
|
if len(multiToken) > 0 {
|
||||||
|
|||||||
17
api/user.go
17
api/user.go
@@ -428,21 +428,14 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
sessionCookie := &http.Cookie{
|
|
||||||
Name: model.SESSION_TOKEN,
|
|
||||||
Value: session.Token,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: maxAge,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, sessionCookie)
|
|
||||||
|
|
||||||
multiToken := ""
|
multiToken := ""
|
||||||
if originalMultiSessionCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
if originalMultiSessionCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
multiToken = originalMultiSessionCookie.Value
|
multiToken = originalMultiSessionCookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("original: " + multiToken)
|
||||||
|
|
||||||
// Attempt to clean all the old tokens or duplicate tokens
|
// Attempt to clean all the old tokens or duplicate tokens
|
||||||
if len(multiToken) > 0 {
|
if len(multiToken) > 0 {
|
||||||
tokens := strings.Split(multiToken, " ")
|
tokens := strings.Split(multiToken, " ")
|
||||||
@@ -463,8 +456,10 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
|
|
||||||
multiToken = strings.TrimSpace(session.Token + " " + multiToken)
|
multiToken = strings.TrimSpace(session.Token + " " + multiToken)
|
||||||
|
|
||||||
|
fmt.Println("new: " + multiToken)
|
||||||
|
|
||||||
multiSessionCookie := &http.Cookie{
|
multiSessionCookie := &http.Cookie{
|
||||||
Name: model.MULTI_SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: multiToken,
|
Value: multiToken,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Respond with an auth token this can be overriden by a specific test as required
|
// Respond with an auth token this can be overriden by a specific test as required
|
||||||
sessionCookie := &http.Cookie{
|
sessionCookie := &http.Cookie{
|
||||||
Name: model.SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: client.AuthToken,
|
Value: client.AuthToken,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
||||||
|
|||||||
@@ -16,17 +16,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HEADER_REQUEST_ID = "X-Request-ID"
|
HEADER_REQUEST_ID = "X-Request-ID"
|
||||||
HEADER_VERSION_ID = "X-Version-ID"
|
HEADER_VERSION_ID = "X-Version-ID"
|
||||||
HEADER_ETAG_SERVER = "ETag"
|
HEADER_ETAG_SERVER = "ETag"
|
||||||
HEADER_ETAG_CLIENT = "If-None-Match"
|
HEADER_ETAG_CLIENT = "If-None-Match"
|
||||||
HEADER_FORWARDED = "X-Forwarded-For"
|
HEADER_FORWARDED = "X-Forwarded-For"
|
||||||
HEADER_REAL_IP = "X-Real-IP"
|
HEADER_REAL_IP = "X-Real-IP"
|
||||||
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
|
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
|
||||||
HEADER_TOKEN = "token"
|
HEADER_TOKEN = "token"
|
||||||
HEADER_BEARER = "BEARER"
|
HEADER_BEARER = "BEARER"
|
||||||
HEADER_AUTH = "Authorization"
|
HEADER_AUTH = "Authorization"
|
||||||
API_URL_SUFFIX = "/api/v1"
|
HEADER_MM_SESSION_TOKEN_HASH = "X-MM-TokenHash"
|
||||||
|
API_URL_SUFFIX = "/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -293,7 +294,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) {
|
|||||||
} else {
|
} else {
|
||||||
c.AuthToken = r.Header.Get(HEADER_TOKEN)
|
c.AuthToken = r.Header.Get(HEADER_TOKEN)
|
||||||
c.AuthType = HEADER_BEARER
|
c.AuthType = HEADER_BEARER
|
||||||
sessionToken := getCookie(SESSION_TOKEN, r)
|
sessionToken := getCookie(SESSION_COOKIE_TOKEN, r)
|
||||||
|
|
||||||
if c.AuthToken != sessionToken.Value {
|
if c.AuthToken != sessionToken.Value {
|
||||||
NewAppError("/users/login", "Authentication tokens didn't match", "")
|
NewAppError("/users/login", "Authentication tokens didn't match", "")
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SESSION_TOKEN = "MMSID"
|
SESSION_COOKIE_TOKEN = "MMTOKEN"
|
||||||
MULTI_SESSION_TOKEN = "MMSIDMU"
|
|
||||||
SESSION_TIME_WEB_IN_DAYS = 30
|
SESSION_TIME_WEB_IN_DAYS = 30
|
||||||
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
|
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
|
||||||
SESSION_TIME_MOBILE_IN_DAYS = 30
|
SESSION_TIME_MOBILE_IN_DAYS = 30
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ function handleError(methodName, xhr, status, err) {
|
|||||||
|
|
||||||
track('api', 'api_weberror', methodName, 'message', msg);
|
track('api', 'api_weberror', methodName, 'message', msg);
|
||||||
|
|
||||||
if (xhr.status === 401) {
|
// if (xhr.status === 401) {
|
||||||
if (window.location.href.indexOf('/channels') === 0) {
|
// if (window.location.href.indexOf('/channels') === 0) {
|
||||||
window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
|
// window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
|
||||||
} else {
|
// } else {
|
||||||
var teamURL = window.location.href.split('/channels')[0];
|
// var teamURL = window.location.href.split('/channels')[0];
|
||||||
window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
|
// window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,6 @@
|
|||||||
<link rel="manifest" href="/static/config/manifest.json">
|
<link rel="manifest" href="/static/config/manifest.json">
|
||||||
<!-- Android add to homescreen -->
|
<!-- Android add to homescreen -->
|
||||||
|
|
||||||
<script>
|
|
||||||
window.mm_config = {{ .ClientCfg }};
|
|
||||||
window.mm_team = {{ .Team }};
|
|
||||||
window.mm_user = {{ .User }};
|
|
||||||
window.mm_session = {{ .Session }};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- CSS Should always go first -->
|
<!-- CSS Should always go first -->
|
||||||
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
|
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
|
||||||
@@ -46,6 +39,16 @@
|
|||||||
|
|
||||||
<style id="antiClickjack">body{display:none !important;}</style>
|
<style id="antiClickjack">body{display:none !important;}</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.mm_config = {{ .ClientCfg }};
|
||||||
|
window.mm_team = {{ .Team }};
|
||||||
|
window.mm_user = {{ .User }};
|
||||||
|
window.mm_session_token_hash = {{ .SessionTokenHash }};
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: { 'X-MM-TokenHash': mm_session_token_hash }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onerror = function(msg, url, line, column, stack) {
|
window.onerror = function(msg, url, line, column, stack) {
|
||||||
var l = {};
|
var l = {};
|
||||||
|
|||||||
36
web/web.go
36
web/web.go
@@ -15,11 +15,7 @@ import (
|
|||||||
"gopkg.in/fsnotify.v1"
|
"gopkg.in/fsnotify.v1"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
<<<<<<< HEAD
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
=======
|
|
||||||
>>>>>>> master
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -48,8 +44,8 @@ func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
|
|||||||
me.User.Sanitize(map[string]bool{})
|
me.User.Sanitize(map[string]bool{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if me.Session != nil {
|
if len(c.Session.Token) > 0 {
|
||||||
me.Session.Sanitize()
|
me.SessionTokenHash = model.HashPassword(c.Session.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
|
if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
|
||||||
@@ -95,9 +91,9 @@ func InitWeb() {
|
|||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
||||||
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/channels/{channelname}", api.UserRequired(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
|
|
||||||
watchAndParseTemplates()
|
watchAndParseTemplates()
|
||||||
}
|
}
|
||||||
@@ -205,7 +201,6 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page := NewHtmlTemplatePage("home", "Home")
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.User = user
|
page.User = user
|
||||||
page.Session = &c.Session
|
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,26 +231,10 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
team = tResult.Data.(*model.Team)
|
team = tResult.Data.(*model.Team)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are already logged into this team then go to town-square
|
|
||||||
if len(c.Session.UserId) != 0 && c.Session.TeamId == team.Id {
|
|
||||||
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We still might be able to switch to this team because we've logged in before
|
// We still might be able to switch to this team because we've logged in before
|
||||||
session := api.FindMultiSessionForTeamId(r, team.Id)
|
session := api.FindMultiSessionForTeamId(r, team.Id)
|
||||||
if session != nil {
|
if session != nil {
|
||||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
sessionCookie := &http.Cookie{
|
|
||||||
Name: model.SESSION_TOKEN,
|
|
||||||
Value: session.Token,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, sessionCookie)
|
|
||||||
|
|
||||||
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -375,6 +354,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
session := api.FindMultiSessionForTeamId(r, team.Id)
|
session := api.FindMultiSessionForTeamId(r, team.Id)
|
||||||
if session == nil {
|
if session == nil {
|
||||||
// redirect to login
|
// redirect to login
|
||||||
|
fmt.Println(">>>>>>>>>>forwarding")
|
||||||
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
||||||
} else {
|
} else {
|
||||||
c.Session = *session
|
c.Session = *session
|
||||||
@@ -449,7 +429,6 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page.Props["UserId"] = c.Session.UserId
|
page.Props["UserId"] = c.Session.UserId
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.User = user
|
page.User = user
|
||||||
page.Session = &c.Session
|
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +657,6 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
page := NewHtmlTemplatePage("home", "Home")
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.User = ruser
|
page.User = ruser
|
||||||
page.Session = &c.Session
|
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -745,7 +723,6 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
page := NewHtmlTemplatePage("home", "Home")
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.User = user
|
page.User = user
|
||||||
page.Session = &c.Session
|
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
|
|
||||||
root(c, w, r)
|
root(c, w, r)
|
||||||
@@ -786,7 +763,6 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||||
page.User = user
|
page.User = user
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.Session = &c.Session
|
|
||||||
page.Props["ActiveTab"] = activeTab
|
page.Props["ActiveTab"] = activeTab
|
||||||
page.Props["TeamId"] = teamId
|
page.Props["TeamId"] = teamId
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user