diff --git a/base/aaa/user.go b/base/aaa/user.go index 4e9da889..6459725b 100644 --- a/base/aaa/user.go +++ b/base/aaa/user.go @@ -14,6 +14,7 @@ func _user_create(m *ice.Message, name string, arg ...string) { const ( BACKGROUND = "background" + AVATAR_URL = "avatar_url" AVATAR = "avatar" GENDER = "gender" MOBILE = "mobile" @@ -22,6 +23,7 @@ const ( COUNTRY = "country" PROVINCE = "province" LANGUAGE = "language" + THEME = "theme" ) const ( USERNICK = "usernick" @@ -46,7 +48,7 @@ func init() { } }}, mdb.CREATE: {Name: "create usernick username* userrole=void,tech userzone background", Hand: func(m *ice.Message, arg ...string) { - _user_create(m, m.Option(USERNAME), m.OptionSimple(USERNICK, USERROLE, USERZONE, BACKGROUND)...) + _user_create(m, m.Option(USERNAME), m.OptionSimple(USERNICK, USERROLE, USERZONE, BACKGROUND, AVATAR, AVATAR_URL, EMAIL, LANGUAGE)...) }}, }, mdb.ImportantHashAction(mdb.SHORT, USERNAME, mdb.FIELD, "time,usernick,username,userrole,userzone"))}, }) diff --git a/base/ctx/config.go b/base/ctx/config.go index 83e42a4b..1267b319 100644 --- a/base/ctx/config.go +++ b/base/ctx/config.go @@ -224,6 +224,11 @@ func ConfigSimple(m *ice.Message, key ...string) (res []string) { return } func ConfigFromOption(m *ice.Message, arg ...string) { + if len(arg) == 0 { + kit.For(m.Target().Commands[m.CommandKey()].Actions[m.ActionKey()].List, func(value ice.Any) { + arg = append(arg, kit.Format(kit.Value(value, mdb.NAME))) + }) + } kit.For(arg, func(k string) { mdb.Config(m, k, kit.Select(mdb.Config(m, k), m.Option(k))) }) } func OptionFromConfig(m *ice.Message, arg ...string) string { diff --git a/base/ctx/display.go b/base/ctx/display.go index b939af97..19ba81fb 100644 --- a/base/ctx/display.go +++ b/base/ctx/display.go @@ -25,7 +25,7 @@ func Display(m displayMessage, file string, arg ...ice.Any) displayMessage { return DisplayBase(m, file, arg...) } func DisplayTable(m displayMessage, arg ...ice.Any) displayMessage { - return DisplayBase(m, "/plugin/table.js", arg...) + return DisplayBase(m, ice.PLUGIN_TABLE_JS, arg...) } func DisplayTableCard(m displayMessage, arg ...ice.Any) displayMessage { return DisplayTable(m, "style", "card") diff --git a/base/web/serve.go b/base/web/serve.go index dfc5e1d9..e15799ae 100644 --- a/base/web/serve.go +++ b/base/web/serve.go @@ -171,7 +171,9 @@ const SERVE = "serve" func init() { Index.MergeCommands(ice.Commands{ - "/exit": {Hand: func(m *ice.Message, arg ...string) { m.Cmd(ice.EXIT) }}, + P(ice.EXIT): {Hand: func(m *ice.Message, arg ...string) { m.Cmd(ice.EXIT) }}, + PP(ice.VOLCANOS): {Hand: func(m *ice.Message, arg ...string) { m.RenderDownload(path.Join(ice.USR_VOLCANOS, path.Join(arg...))) }}, + PP(ice.INTSHELL): {Hand: func(m *ice.Message, arg ...string) { m.RenderDownload(path.Join(ice.USR_INTSHELL, path.Join(arg...))) }}, SERVE: {Name: "serve name auto start dark system", Help: "服务器", Actions: ice.MergeActions(ice.Actions{ ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) { cli.NodeInfo(m, ice.Info.Pathname, WORKER) @@ -227,7 +229,13 @@ func Domain(host, port string) string { return kit.Format("%s://%s:%s", HTTP, ho func Script(m *ice.Message, str string, arg ...ice.Any) string { return ice.Render(m, ice.RENDER_SCRIPT, kit.Format(str, arg...)) } -func ChatCmdPath(arg ...string) string { return path.Join("/chat/cmd/", path.Join(arg...)) } +func ChatCmdPath(m *ice.Message, arg ...string) string { + if p := m.Option(ice.MSG_USERPOD); p != "" { + return path.Join("/chat/pod/", p, "/cmd/", kit.Select(m.PrefixKey(), path.Join(arg...))) + + } + return path.Join("/chat/cmd/", kit.Select(m.PrefixKey(), path.Join(arg...))) +} func RequireFile(m *ice.Message, file string) string { if strings.HasPrefix(file, nfs.PS) || strings.HasPrefix(file, ice.HTTP) { return file diff --git a/base/web/spide.go b/base/web/spide.go index 95a59994..cd0f32b4 100644 --- a/base/web/spide.go +++ b/base/web/spide.go @@ -61,7 +61,7 @@ func _spide_show(m *ice.Message, name string, arg ...string) { m.Logs("response", v.Name, v.Value) }) }) - if m.Warn(res.StatusCode != http.StatusOK, ice.ErrNotValid, uri, cli.STATUS, res.Status) { + if m.Warn(res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated, ice.ErrNotValid, uri, cli.STATUS, res.Status) { switch res.StatusCode { case http.StatusNotFound, http.StatusUnauthorized: return @@ -100,7 +100,9 @@ func _spide_body(m *ice.Message, method string, arg ...string) (io.Reader, ice.M default: data := ice.Map{} kit.For(arg, func(k, v string) { kit.Value(data, k, v) }) - head[ContentType], body = ApplicationJSON, bytes.NewBufferString(kit.Format(data)) + _data := kit.Format(data) + m.Debug("post %v %v", len(_data), _data) + head[ContentType], body = ApplicationJSON, bytes.NewBufferString(_data) } return body, head, arg[:0] } @@ -168,6 +170,7 @@ func _spide_send(m *ice.Message, name string, req *http.Request, timeout string) func _spide_save(m *ice.Message, action, file, uri string, res *http.Response) { switch action { case SPIDE_RAW: + m.SetResult() if b, _ := ioutil.ReadAll(res.Body); strings.HasPrefix(res.Header.Get(ContentType), ApplicationJSON) { m.Echo(kit.Formats(kit.UnMarshal(string(b)))) } else { @@ -209,14 +212,15 @@ const ( SPIDE_JSON = "json" SPIDE_RES = "content_data" - Basic = "Basic" - Bearer = "Bearer" - Authorization = "Authorization" - ContentType = "Content-Type" - ContentLength = "Content-Length" - UserAgent = "User-Agent" - Referer = "Referer" - Accept = "Accept" + Basic = "Basic" + Bearer = "Bearer" + Authorization = "Authorization" + AcceptLanguage = "Accept-Language" + ContentLength = "Content-Length" + ContentType = "Content-Type" + UserAgent = "User-Agent" + Referer = "Referer" + Accept = "Accept" ContentFORM = "application/x-www-form-urlencoded" ContentPNG = "image/png" @@ -256,6 +260,9 @@ func init() { m.Cmd("", mdb.CREATE, ice.COM, kit.Select("https://contexts.com.cn", conf[cli.CTX_COM])) m.Cmd("", mdb.CREATE, ice.SHY, kit.Select(kit.Select("https://shylinux.com", ice.Info.Make.Remote), conf[cli.CTX_SHY])) }}, + mdb.INPUTS: {Hand: func(m *ice.Message, arg ...string) { + mdb.HashSelectValue(m, func(value ice.Map) { m.Push(kit.Select(ORIGIN, arg, 0), value[ORIGIN]) }) + }}, mdb.SEARCH: {Hand: func(m *ice.Message, arg ...string) { if mdb.IsSearchPreview(m, arg) { m.PushSearch(mdb.TYPE, LINK, mdb.NAME, ice.DEV, mdb.TEXT, mdb.HashSelectField(m, ice.DEV, CLIENT_ORIGIN)) diff --git a/base/web/token.go b/base/web/token.go index 425a0c11..b1a302f5 100644 --- a/base/web/token.go +++ b/base/web/token.go @@ -30,7 +30,7 @@ func init() { }}, CONFIRM: {Hand: func(m *ice.Message, arg ...string) { msg := m.Cmd("", mdb.CREATE, mdb.TYPE, Basic, mdb.NAME, m.Option(ice.MSG_USERNAME), mdb.TEXT, m.Option(tcp.HOST)) - m.ProcessReplace(kit.MergeURL2(m.Option(tcp.HOST), ChatCmdPath(m.PrefixKey(), SET), + m.ProcessReplace(kit.MergeURL2(m.Option(tcp.HOST), ChatCmdPath(m, m.PrefixKey(), SET), TOKEN, strings.Replace(UserHost(m), "://", kit.Format("://%s:%s@", m.Option(ice.MSG_USERNAME), msg.Result()), 1))) }}, SET: {Hand: func(m *ice.Message, arg ...string) { diff --git a/conf.go b/conf.go index 2636c2c5..95eee5ea 100644 --- a/conf.go +++ b/conf.go @@ -100,12 +100,13 @@ const ( // DIR INDEX_SH = "index.sh" FAVICON_ICO = "/favicon.ico" - PLUGIN_INPUT = "/plugin/input/" - PLUGIN_LOCAL = "/plugin/local/" - PLUGIN_STORY = "/plugin/story/" - REQUIRE_SRC = "/require/src/" - REQUIRE_USR = "/require/usr/" + PLUGIN_INPUT = "/volcanos/plugin/input/" + PLUGIN_LOCAL = "/volcanos/plugin/local/" + PLUGIN_STORY = "/volcanos/plugin/story/" + PLUGIN_TABLE_JS = "/volcanos/plugin/table.js" REQUIRE_MODULES = "/require/modules/" + REQUIRE_USR = "/require/usr/" + REQUIRE_SRC = "/require/src/" ISH_PLUGED = ".ish/pluged/" USR_MODULES = "usr/node_modules/" diff --git a/core/chat/header.go b/core/chat/header.go index 2bbd5faa..acecb04a 100644 --- a/core/chat/header.go +++ b/core/chat/header.go @@ -1,12 +1,15 @@ package chat import ( + "path" + ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/aaa" "shylinux.com/x/icebergs/base/cli" "shylinux.com/x/icebergs/base/ctx" "shylinux.com/x/icebergs/base/gdb" "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/nfs" "shylinux.com/x/icebergs/base/tcp" "shylinux.com/x/icebergs/base/web" "shylinux.com/x/icebergs/base/web/html" @@ -85,14 +88,16 @@ func init() { m.Cmd(cli.SYSTEM, "osascript", "-e", `tell app "System Events" to tell appearance preferences to set dark mode to `+ kit.Select(ice.FALSE, ice.TRUE, kit.IsIn(kit.Select(html.DARK, arg, 0), html.DARK, html.BLACK))) }}, - }, ctx.ConfAction(SSO, "", aaa.LANGUAGE, "zh")), Hand: func(m *ice.Message, arg ...string) { + }, ctx.ConfAction(SSO, "")), Hand: func(m *ice.Message, arg ...string) { + m.Option("language.list", m.Cmd(nfs.DIR, path.Join(ice.SRC_TEMPLATE, m.PrefixKey(), aaa.LANGUAGE), nfs.FILE).Appendv(nfs.FILE)) + m.Option("theme.list", m.Cmd(nfs.DIR, path.Join(ice.SRC_TEMPLATE, m.PrefixKey(), aaa.THEME), nfs.FILE).Appendv(nfs.FILE)) if gdb.Event(m, HEADER_AGENT); !_header_check(m, arg...) { return } msg := m.Cmd(aaa.USER, m.Option(ice.MSG_USERNAME)) kit.For([]string{aaa.USERNICK, aaa.LANGUAGE}, func(k string) { m.Option(k, msg.Append(k)) }) kit.For([]string{aaa.AVATAR, aaa.BACKGROUND}, func(k string) { m.Option(k, web.RequireFile(m, msg.Append(k))) }) - kit.If(m.Option(aaa.LANGUAGE) == "", func() { m.Option(aaa.LANGUAGE, mdb.Config(m, aaa.LANGUAGE)) }) + kit.If(m.Option(aaa.LANGUAGE) == "", func() { m.Option(aaa.LANGUAGE, kit.Split(m.R.Header.Get(web.AcceptLanguage), ",;")[0]) }) m.Echo(mdb.Config(m, TITLE)).Option(MENUS, mdb.Config(m, MENUS)) }}, }) diff --git a/core/chat/oauth/oauth.go b/core/chat/oauth.bak/oauth.go similarity index 100% rename from core/chat/oauth/oauth.go rename to core/chat/oauth.bak/oauth.go diff --git a/core/chat/oauth.bak/oauth.shy b/core/chat/oauth.bak/oauth.shy new file mode 100644 index 00000000..5e84cb19 --- /dev/null +++ b/core/chat/oauth.bak/oauth.shy @@ -0,0 +1,18 @@ +section "oauth" +refer ` +官网 https://oauth.net/2/ +标准 http://www.rfcreader.com/#rfc6749 +博客 https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html +服务 https://developers.google.com/identity/protocols/oauth2 +服务 https://docs.github.com/cn/developers/apps/building-oauth-apps/authorizing-oauth-apps +应用 https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/github/ +` + +field web.chat.oauth.apply +field web.chat.oauth.reply +field web.chat.oauth.offer + +field web.chat.oauth.authorize +field web.chat.oauth.token +field web.chat.oauth.access + diff --git a/core/chat/oauth/client.go b/core/chat/oauth/client.go new file mode 100644 index 00000000..4166421b --- /dev/null +++ b/core/chat/oauth/client.go @@ -0,0 +1,134 @@ +package oauth + +import ( + "path" + "strings" + + "shylinux.com/x/ice" + "shylinux.com/x/icebergs/base/aaa" + "shylinux.com/x/icebergs/base/ctx" + "shylinux.com/x/icebergs/base/lex" + "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/web" + kit "shylinux.com/x/toolkits" +) + +const ( + CLIENT_ID = "client_id" + CLIENT_SECRET = "client_secret" + + OAUTH_URL = "oauth_url" + GRANT_URL = "grant_url" + TOKEN_URL = "token_url" + USERS_URL = "users_url" + + REDIRECT_URI = "redirect_uri" + RESPONSE_TYPE = "response_type" + AUTHORIZATION_CODE = "authorization_code" + GRANT_TYPE = "grant_type" + STATE = "state" + CODE = "code" + API = "api" + + ACCESS_TOKEN = "access_token" + EXPIRES_IN = "expires_in" +) + +type Client struct { + ice.Hash + short string `data:"domain,client_id"` + field string `data:"time,hash,domain,client_id,client_secret,oauth_url,grant_url,token_url,users_url,api,prefix"` + auth string `name:"auth" help:"授权"` + user string `name:"user" help:"用户"` + orgs string `name:"orgs" help:"组织"` + repo string `name:"repo" help:"源码"` + list string `name:"list hash auto"` +} + +func (s Client) Init(m *ice.Message, arg ...string) { + aaa.White(m.Message, m.PrefixKey(), ctx.ACTION, aaa.LOGIN) + s.Hash.Init(m, arg...) +} +func (s Client) Inputs(m *ice.Message, arg ...string) { + switch s.Hash.Inputs(m, arg...); arg[0] { + case web.DOMAIN: + m.Cmdy(web.SPIDE, mdb.INPUTS, arg) + m.Push(arg[0], "https://repos.shylinux.com") + case OAUTH_URL: + m.Push(arg[0], "/login/oauth/authorize") + case GRANT_URL: + m.Push(arg[0], "/login/oauth/access_token") + case TOKEN_URL: + m.Push(arg[0], "/login/oauth/access_token") + case USERS_URL: + m.Push(arg[0], "/api/v1/user") + case lex.PREFIX: + m.Push(arg[0], "token") + case API: + m.Push(arg[0], "/api/v1/") + } +} +func (s Client) Auth(m *ice.Message, arg ...string) { + m.Options(REDIRECT_URI, s.RedirectURI(m), RESPONSE_TYPE, CODE, STATE, m.Option(mdb.HASH)) + m.ProcessOpen(kit.MergeURL2(m.Option(web.DOMAIN), m.Option(OAUTH_URL), m.OptionSimple(CLIENT_ID, REDIRECT_URI, RESPONSE_TYPE, STATE))) +} +func (s Client) User(m *ice.Message, arg ...string) { + if res := s.Get(m, m.Option(mdb.HASH), m.Option(USERS_URL)); res != nil { + m.Options(res).Cmd(aaa.USER, mdb.CREATE, aaa.USERNICK, m.Option("full_name"), m.OptionSimple(aaa.USERNAME), + aaa.USERROLE, kit.Select(aaa.VOID, aaa.TECH, m.Option("is_admin") == ice.TRUE), aaa.USERZONE, m.Option(web.DOMAIN), + m.OptionSimple(aaa.EMAIL, aaa.LANGUAGE, aaa.AVATAR_URL)) + } +} +func (s Client) Orgs(m *ice.Message, arg ...string) {} +func (s Client) Repo(m *ice.Message, arg ...string) {} +func (s Client) List(m *ice.Message, arg ...string) { + if s.Hash.List(m, arg...).PushAction(s.User, s.Auth, s.Remove); len(arg) == 0 { + m.EchoScript(s.RedirectURI(m)) + } else { + m.EchoScript("config header sso " + kit.MergeURL2(m.Append(web.DOMAIN), m.Append(OAUTH_URL), m.AppendSimple(CLIENT_ID), REDIRECT_URI, s.RedirectURI(m), RESPONSE_TYPE, CODE, STATE, arg[0])) + } +} + +func init() { ice.ChatCtxCmd(Client{}) } + +func (s Client) RedirectURI(m *ice.Message) string { + return strings.Split(web.MergeURL2(m, web.ChatCmdPath(m.Message, m.PrefixKey(), ctx.ACTION, aaa.LOGIN)), "?")[0] +} +func (s Client) Login(m *ice.Message, arg ...string) { + if state, code := m.Option(STATE), m.Option(CODE); !m.Warn(state == "" || code == "") { + s.Hash.List(m.Spawn(), m.Option(mdb.HASH, state)).Table(func(value ice.Maps) { m.Options(value) }) + m.Options(REDIRECT_URI, s.RedirectURI(m), GRANT_TYPE, AUTHORIZATION_CODE) + if res := s.Post(m, m.Option(mdb.HASH), m.Option(GRANT_URL), m.OptionSimple(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, CODE, GRANT_TYPE)...); !m.Warn(res == nil) { + kit.Value(res, EXPIRES_IN, m.Time(kit.Format("%vs", kit.Value(res, EXPIRES_IN)))) + s.Modify(m, kit.Simple(res)...) + m.Options(res) + if s.User(m); !m.Warn(m.Option(aaa.USERNAME) == "") && m.R != nil { + web.RenderCookie(m.Message, aaa.SessCreate(m.Message, m.Option(aaa.USERNAME))) + m.ProcessHistory() + } else { + m.ProcessClose() + } + } + } +} + +func (s Client) Get(m *ice.Message, hash, api string, arg ...string) ice.Any { + return web.SpideGet(m.Message, s.request(m, hash, api, arg...)) +} +func (s Client) Put(m *ice.Message, hash, api string, arg ...string) ice.Any { + return web.SpidePut(m.Message, s.request(m, hash, api, arg...)) +} +func (s Client) Post(m *ice.Message, hash, api string, arg ...string) ice.Any { + return web.SpidePost(m.Message, s.request(m, hash, api, arg...)) +} +func (s Client) Delete(m *ice.Message, hash, api string, arg ...string) ice.Any { + return web.SpideDelete(m.Message, s.request(m, hash, api, arg...)) +} +func (s Client) request(m *ice.Message, hash, api string, arg ...string) []string { + msg := s.Hash.List(m.Spawn(), hash) + kit.If(msg.Append(ACCESS_TOKEN), func(p string) { + m.Options(web.SPIDE_HEADER, ice.Maps{web.Authorization: msg.Append(lex.PREFIX) + lex.SP + p}) + }) + kit.If(api == "", func() { api = path.Join(msg.Append(API), strings.ToLower(kit.FuncName(6))) }) + return kit.Simple(kit.MergeURL2(msg.Append(web.DOMAIN), api), arg) +} diff --git a/core/chat/oauth/oauth.shy b/core/chat/oauth/oauth.shy index 5e84cb19..71f99b8d 100644 --- a/core/chat/oauth/oauth.shy +++ b/core/chat/oauth/oauth.shy @@ -1,18 +1,6 @@ -section "oauth" +chapter "oauth" refer ` -官网 https://oauth.net/2/ -标准 http://www.rfcreader.com/#rfc6749 -博客 https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html -服务 https://developers.google.com/identity/protocols/oauth2 -服务 https://docs.github.com/cn/developers/apps/building-oauth-apps/authorizing-oauth-apps -应用 https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/github/ +https://oauth.net/2/ +https://datatracker.ietf.org/doc/html/rfc6749 +https://datatracker.ietf.org/doc/html/rfc5849 ` - -field web.chat.oauth.apply -field web.chat.oauth.reply -field web.chat.oauth.offer - -field web.chat.oauth.authorize -field web.chat.oauth.token -field web.chat.oauth.access - diff --git a/core/team/task.go b/core/team/task.go index 25208ae3..b2ede877 100644 --- a/core/team/task.go +++ b/core/team/task.go @@ -6,6 +6,7 @@ import ( ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/ctx" "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/nfs" "shylinux.com/x/icebergs/base/web" kit "shylinux.com/x/toolkits" ) @@ -90,6 +91,9 @@ func init() { default: mdb.ZoneInputs(m, arg) } + if arg[0] == mdb.ZONE { + m.Push(arg[0], kit.Split(nfs.TemplateText(m, mdb.ZONE))) + } }}, mdb.INSERT: {Name: "insert space zone* type=once,step,week name* text begin_time@date close_time@date", Hand: func(m *ice.Message, arg ...string) { if space, arg := arg[1], arg[2:]; space != "" { diff --git a/misc.go b/misc.go index 79afa7fe..292d2321 100644 --- a/misc.go +++ b/misc.go @@ -97,6 +97,14 @@ func (m *Message) RenameAppend(arg ...string) *Message { }) return m } +func (m *Message) RewriteAppend(cb func(value, key string, index int) string) *Message { + m.Table(func(index int, value Maps, head []string) { + for _, key := range head { + m.meta[key][index] = cb(value[key], key, index) + } + }) + return m +} func (m *Message) ToLowerAppend(arg ...string) *Message { kit.For(m.meta[MSG_APPEND], func(k string) { m.RenameAppend(k, strings.ToLower(k)) }) return m diff --git a/misc/git/status.go b/misc/git/status.go index 541b1c8c..48fdacd0 100644 --- a/misc/git/status.go +++ b/misc/git/status.go @@ -142,7 +142,7 @@ func init() { kit.If(m.Option(web.TOKEN), func() { m.Cmd(web.TOKEN, "set") }) }}, OAUTH: {Help: "授权", Hand: func(m *ice.Message, arg ...string) { - m.ProcessOpen(kit.MergeURL2(kit.Select(ice.Info.Make.Domain, m.Cmdx(REPOS, "remoteURL")), web.ChatCmdPath(web.TOKEN, "gen"), tcp.HOST, m.Option(ice.MSG_USERWEB))) + m.ProcessOpen(kit.MergeURL2(kit.Select(ice.Info.Make.Domain, m.Cmdx(REPOS, "remoteURL")), web.ChatCmdPath(m, web.TOKEN, "gen"), tcp.HOST, m.Option(ice.MSG_USERWEB))) }}, INSTEADOF: {Name: "insteadof remote", Help: "代理", Hand: func(m *ice.Message, arg ...string) { m.Cmdy(REPOS, INSTEADOF, arg)