ソースを参照

Merge pull request #575 from kpjhg0124/master

oauth client (로그인) 기능 추가
Surplus_UP (2DU) 7 年 前
コミット
3666f59c90
7 ファイル変更366 行追加118 行削除
  1. 146 4
      app.py
  2. 13 0
      func.py
  3. 14 1
      language/en-US.json
  4. 126 112
      language/ko-KR.json
  5. 16 0
      oauthsettings.json
  6. 1 1
      readme.md
  7. 50 0
      views/oauth.css

+ 146 - 4
app.py

@@ -90,6 +90,7 @@ curs.execute('create table if not exists scan(test text)')
 curs.execute('create table if not exists acl(test text)')
 curs.execute('create table if not exists acl(test text)')
 curs.execute('create table if not exists inter(test text)')
 curs.execute('create table if not exists inter(test text)')
 curs.execute('create table if not exists html_filter(test text)')
 curs.execute('create table if not exists html_filter(test text)')
+curs.execute('create table if not exists oauth_conn(test text)')
 
 
 if setup_tool == 0:
 if setup_tool == 0:
     curs.execute('select data from other where name = "ver"')
     curs.execute('select data from other where name = "ver"')
@@ -124,7 +125,8 @@ if setup_tool != 0:
         'scan', 
         'scan', 
         'acl', 
         'acl', 
         'inter', 
         'inter', 
-        'html_filter'
+        'html_filter',
+        'oauth_conn'
     ]
     ]
 
 
     create_data['data'] = ['title', 'data']
     create_data['data'] = ['title', 'data']
@@ -148,6 +150,7 @@ if setup_tool != 0:
     create_data['acl'] = ['title', 'dec', 'dis', 'view', 'why']
     create_data['acl'] = ['title', 'dec', 'dis', 'view', 'why']
     create_data['inter'] = ['title', 'link']
     create_data['inter'] = ['title', 'link']
     create_data['html_filter'] = ['html', 'kind']
     create_data['html_filter'] = ['html', 'kind']
+    create_data['oauth_conn'] = ['provider', 'wiki_id', 'sns_id', 'name', 'picture']
 
 
     for create_table in create_data['all_data']:
     for create_table in create_data['all_data']:
         for create in create_data[create_table]:
         for create in create_data[create_table]:
@@ -1284,6 +1287,25 @@ def now_update():
         data = 'auto update is not support. <a href="https://github.com/2DU/opennamu">(github)</a>',
         data = 'auto update is not support. <a href="https://github.com/2DU/opennamu">(github)</a>',
         menu = [['manager/1', load_lang('admin')]]
         menu = [['manager/1', load_lang('admin')]]
     ))
     ))
+
+#OAuth Developing (hoparkgo9ma)
+@app.route('/oauth_settings')
+def oauth_settings():
+    if admin_check(None, 'indexing') != 1:
+        return re_error('/error/3')
+    oauth_supported = load_oauth('_README')['support']
+    body_content = '<form action="" accept-charset="utf-8" name="" method="post">'
+    for i in range(len(oauth_supported)):
+        oauth_data = load_oauth(oauth_supported[i])
+        for j in range(2):
+            if j == 0:
+                load_target = 'id'
+            elif j == 1:
+                load_target = 'secret'
+            body_content += '<input id="{}_client_{}" type="checkbox"><input placeholder="{}_client_{}" name="{}_client_{}" value="{}" type="text" style="width: 80%;"><hr>'.format(oauth_supported[i], load_target, oauth_supported[i], load_target, oauth_supported[i], load_target, oauth_data['client_{}'.format(load_target)])
+    body_content += '<button id="save" type="submit">' + load_lang('save') + '</button></form>'
+    return easy_minify(flask.render_template(skin_check(), imp = [load_lang('oauth_settings'), wiki_set(), custom(), other2([0, 0])], data = body_content, menu = [['other', load_lang('other')]]))
+
         
         
 @app.route('/xref/<everything:name>')
 @app.route('/xref/<everything:name>')
 def xref(name = None):
 def xref(name = None):
@@ -2133,6 +2155,7 @@ def manager(num = 1):
                         <li><a href="/indexing">''' + load_lang('indexing') + ' (' + load_lang('create') + ' or ' + load_lang('delete') + ''')</a></li>
                         <li><a href="/indexing">''' + load_lang('indexing') + ' (' + load_lang('create') + ' or ' + load_lang('delete') + ''')</a></li>
                         <li><a href="/restart">''' + load_lang('server') + ' ' + load_lang('restart') + '''</a></li>
                         <li><a href="/restart">''' + load_lang('server') + ' ' + load_lang('restart') + '''</a></li>
                         <li><a href="/update">''' + load_lang('update') + '''</a></li>
                         <li><a href="/update">''' + load_lang('update') + '''</a></li>
+                        <li><a href="/oauth_settings">''' + load_lang('oauth_settings') + '''</a></li>
                     </ul>
                     </ul>
                     ''',
                     ''',
             menu = [['other', load_lang('other')]]
             menu = [['other', load_lang('other')]]
@@ -2700,6 +2723,7 @@ def close_topic_list(name = None, tool = None):
         
         
 @app.route('/login', methods=['POST', 'GET'])
 @app.route('/login', methods=['POST', 'GET'])
 def login():
 def login():
+    print(flask.request.referrer)
     if custom()[2] != 0:
     if custom()[2] != 0:
         return redirect('/user')
         return redirect('/user')
     
     
@@ -2744,7 +2768,14 @@ def login():
         conn.commit()
         conn.commit()
         
         
         return redirect('/user')  
         return redirect('/user')  
-    else:        
+    else:
+        oauth_content = '<div class="oauth-wrapper"><ul class="oauth-list">'
+        oauth_supported = load_oauth('_README')['support']
+        for i in range(len(oauth_supported)):
+            oauth_data = load_oauth(oauth_supported[i])
+            if oauth_data['client_id'] != '' and oauth_data['client_secret'] != '':
+                oauth_content += '<link rel="stylesheet" href="/views/oauth.css"><li><a href="/oauth/{}/init"><div class="oauth-btn oauth-btn-{}"><div class="oauth-btn-logo oauth-btn-{}"></div>{}</div></a></li>'.format(oauth_supported[i], oauth_supported[i], oauth_supported[i], load_lang('oauth_signin_' + oauth_supported[i]))
+        oauth_content += '</ul></div>'
         return easy_minify(flask.render_template(skin_check(),    
         return easy_minify(flask.render_template(skin_check(),    
             imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
             imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
             data =  '''
             data =  '''
@@ -2754,13 +2785,110 @@ def login():
                         <input placeholder="''' + load_lang('password') + '''" name="pw" type="password">
                         <input placeholder="''' + load_lang('password') + '''" name="pw" type="password">
                         <hr class=\"main_hr\">
                         <hr class=\"main_hr\">
                         ''' + captcha_get() + '''
                         ''' + captcha_get() + '''
-                        <button type="submit">''' + load_lang('login') + '''</button>
+                        <button type="submit">''' + load_lang('login') + '''</button><a href="/register">''' + load_lang('register_suggest') + '''</a>
+                        <hr class=\"main_hr\">
+                        ''' + oauth_content + '''
                         <hr class=\"main_hr\">
                         <hr class=\"main_hr\">
                         <span>''' + load_lang('http_warring') + '''</span>
                         <span>''' + load_lang('http_warring') + '''</span>
                     </form>
                     </form>
                     ''',
                     ''',
             menu = [['user', load_lang('user')]]
             menu = [['user', load_lang('user')]]
         ))
         ))
+
+@app.route('/oauth/<regex("naver|facebook"):platform>/<regex("init|callback"):func>', methods=['GET', 'POST'])
+def login_oauth(platform = None, func = None):
+    publish_url = load_oauth('publish_url')
+    oauth_data = load_oauth(platform)
+    api_url = {}
+    data = {
+        'client_id' : oauth_data['client_id'],
+        'client_secret' : oauth_data['client_secret'],
+        'redirect_uri' : publish_url + '/oauth/' + platform + '/callback',
+        'state' : 'RAMDOMVALUE'
+    }
+
+    if platform == 'naver':
+        api_url['redirect'] = 'https://nid.naver.com/oauth2.0/authorize'
+        api_url['token'] = 'https://nid.naver.com/oauth2.0/token'
+        api_url['profile'] = 'https://openapi.naver.com/v1/nid/me'
+    elif platform == 'facebook':
+        api_url['redirect'] = 'https://www.facebook.com/v3.1/dialog/oauth'
+        api_url['token'] = 'https://graph.facebook.com/v3.1/oauth/access_token'
+        api_url['profile'] = 'https://graph.facebook.com/me'
+
+    if func == 'init':
+        if oauth_data['client_id'] == '' or oauth_data['client_secret'] == '':
+            return easy_minify(flask.render_template(skin_check(), imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])], data = load_lang('oauth_disabled'), menu = [['user', load_lang('user')]]))
+        elif publish_url == 'https://':
+            return easy_minify(flask.render_template(skin_check(), imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])], data = load_lang('oauth_settings_not_found'), menu = [['user', load_lang('user')]]))
+
+        referrer_re = re.compile(r'(?P<host>^(https?):\/\/([^\/]+))\/(?P<refer>[^\/?]+)')
+        if flask.request.referrer != None:
+            referrer = referrer_re.search(flask.request.referrer)
+            if referrer.group('host') != load_oauth('publish_url'):
+                return redirect('/')
+            else:
+                flask.session['referrer'] = referrer.group('refer')
+        else:
+            return redirect('/')
+        flask.session['refer'] = flask.request.referrer
+
+        if platform == 'naver':
+            return redirect(api_url['redirect']+'?response_type=code&client_id={}&redirect_uri={}&state={}'.format(data['client_id'], data['redirect_uri'], data['state']))
+        elif platform == 'facebook':
+            return redirect(api_url['redirect']+'?client_id={}&redirect_uri={}&state={}'.format(data['client_id'], data['redirect_uri'], data['state']))
+
+    elif func == 'callback':
+        code = flask.request.args.get('code')
+        state = flask.request.args.get('state')
+        if code == None or state == None:
+            return easy_minify(flask.render_template(skin_check(), imp = [load_lang('inter_error'), wiki_set(), custom(), other2([0, 0])], data = 
+            '''<p>''' + load_lang('inter_error_detail') + '''</p>
+            <hr>
+            <code>ie_wrong_callback</code>
+            <p>''' + load_lang('ie_wrong_callback') + '''</p>
+            '''
+            , menu = [['user', load_lang('user')]]))
+
+        if platform == 'naver':
+            token_access = api_url['token']+'?grant_type=authorization_code&client_id={}&client_secret={}&code={}&state={}'.format(data['client_id'], data['client_secret'], code, state)
+            token_result = urllib.request.urlopen(token_access).read().decode('utf-8')
+            token_result_json = json.loads(token_result)
+
+            headers = {'Authorization': 'Bearer {}'.format(token_result_json['access_token'])}
+            profile_access = urllib.request.Request(api_url['profile'], headers = headers)
+            profile_result = urllib.request.urlopen(profile_access).read().decode('utf-8')
+            profile_result_json = json.loads(profile_result)
+
+            stand_json = {'id' : profile_result_json['response']['id'], 'name' : profile_result_json['response']['name'], 'picture' : profile_result_json['response']['profile_image']}
+        elif platform == 'facebook':
+            token_access = api_url['token']+'?client_id={}&redirect_uri={}&client_secret={}&code={}'.format(data['client_id'], data['redirect_uri'], data['client_secret'], code)
+            token_result = urllib.request.urlopen(token_access).read().decode('utf-8')
+            token_result_json = json.loads(token_result)
+
+            profile_access = api_url['profile']+'?fields=id,name,picture&access_token={}'.format(token_result_json['access_token'])
+            profile_result = urllib.request.urlopen(profile_access).read().decode('utf-8')
+            profile_result_json = json.loads(profile_result)
+
+            stand_json = {'id': profile_result_json['id'], 'name': profile_result_json['name'], 'picture': profile_result_json['picture']['data']['url']}
+        
+        if flask.session['referrer'][0:6] == 'change':
+            curs.execute('select * from oauth_conn where wiki_id = ? and provider = ?', [flask.session['id'], platform])
+            oauth_result = curs.fetchall()
+            if len(oauth_result) == 0:
+                curs.execute('insert into oauth_conn (provider, wiki_id, sns_id, name, picture) values(?, ?, ?, ?, ?)', [platform, flask.session['id'], stand_json['id'], stand_json['name'], stand_json['picture']])
+            else:
+                curs.execute('update oauth_conn set name = ? picture = ? where wiki_id = ?', [stand_json['name'], stand_json['pricture'], flask.session['id']])
+            conn.commit()
+        elif flask.session['referrer'][0:5] == 'login':
+            curs.execute('select * from oauth_conn where provider = ? and sns_id = ?', [platform, stand_json['id']])
+            curs_result = curs.fetchall()
+            if len(curs_result) == 0:
+                return re_error('/error/2')
+            else:
+                flask.session['state'] = 1
+                flask.session['id'] = curs_result[0][2]
+        return redirect(flask.session['refer'])
                 
                 
 @app.route('/change', methods=['POST', 'GET'])
 @app.route('/change', methods=['POST', 'GET'])
 def change_password():
 def change_password():
@@ -2824,7 +2952,7 @@ def change_password():
             div3 = ''
             div3 = ''
             var_div3 = ''
             var_div3 = ''
 
 
-            curs.execute('select data from user_set where name = "lang" and id = ?', [ip])
+            curs.execute('select data from user_set where name = "lang" and id = ?', [flask.session['id']])
             data = curs.fetchall()
             data = curs.fetchall()
 
 
             for lang_data in support_language:
             for lang_data in support_language:
@@ -2835,6 +2963,17 @@ def change_password():
 
 
             div3 += var_div3
             div3 += var_div3
 
 
+            oauth_provider = load_oauth('_README')['support']
+            oauth_content = '<ul>'
+            for i in range(len(oauth_provider)):
+                curs.execute('select * from oauth_conn where wiki_id = ? and provider = ?', [flask.session['id'], oauth_provider[i]])
+                oauth_data = curs.fetchall()
+                if len(oauth_data) == 1:
+                    oauth_content += '<li>{} - {}</li>'.format(oauth_provider[i], load_lang('connection') + load_lang('oauth_conn_done') + ': <img src="{}" width="17px" height="17px">{}'.format(oauth_data[0][5], oauth_data[0][4]))
+                else:
+                    oauth_content += '<li>{} - {}</li>'.format(oauth_provider[i], load_lang('connection') + load_lang('oauth_conn_not') + '. <a href="/oauth/{}/init">{}</a>'.format(oauth_provider[i], load_lang('oauth_conn_new')))
+            oauth_content += '</ul>'
+
             return easy_minify(flask.render_template(skin_check(),    
             return easy_minify(flask.render_template(skin_check(),    
                 imp = [load_lang('user') + ' ' + load_lang('setting') + ' ' + load_lang('edit'), wiki_set(), custom(), other2([0, 0])],
                 imp = [load_lang('user') + ' ' + load_lang('setting') + ' ' + load_lang('edit'), wiki_set(), custom(), other2([0, 0])],
                 data =  '''
                 data =  '''
@@ -2859,6 +2998,9 @@ def change_password():
                             <br>
                             <br>
                             <select name="lang">''' + div3 + '''</select>
                             <select name="lang">''' + div3 + '''</select>
                             <hr class=\"main_hr\">
                             <hr class=\"main_hr\">
+                            <span>OAuth ''' + load_lang('connection') + '''</span>
+                            ''' + oauth_content + '''
+                            <hr class=\"main_hr\">
                             <button type="submit">''' + load_lang('edit') + '''</button>
                             <button type="submit">''' + load_lang('edit') + '''</button>
                             <hr class=\"main_hr\">
                             <hr class=\"main_hr\">
                             <span>''' + load_lang('http_warring') + '''</span>
                             <span>''' + load_lang('http_warring') + '''</span>

+ 13 - 0
func.py

@@ -265,6 +265,19 @@ def load_lang(data, num = 2):
         else:
         else:
             return load_lang(data, 1)
             return load_lang(data, 1)
 
 
+def load_oauth(provider):
+    oauth_native = open('oauthsettings.json', encoding='utf-8').read()
+    oauth = json.loads(oauth_native)
+    return oauth[provider]
+
+def update_oauth(provider, target, content):
+    oauth_native = open('oauthsettings.json', encoding='utf-8').read()
+    oauth = json.loads(oauth_native)
+    oauth[provider][target] = content
+    with open('oauthsettings.json', 'w', encoding='utf-8') as f:
+        json.dump(oauth, f)
+    return 'Done'
+
 def ip_or_user(data):
 def ip_or_user(data):
     if re.search('(\.|:)', data):
     if re.search('(\.|:)', data):
         return 1
         return 1

+ 14 - 1
language/en-US.json

@@ -49,6 +49,7 @@
     "license" : "license",
     "license" : "license",
     "interwiki" : "interwiki",
     "interwiki" : "interwiki",
     "update" : "update",
     "update" : "update",
+    "oauth_settings" : "OAuth Settings",
     "setting" : "set",
     "setting" : "set",
     "create" : "create",
     "create" : "create",
     "editor" : "editor",
     "editor" : "editor",
@@ -77,8 +78,9 @@
     "login" : "login",
     "login" : "login",
     "logout" : "logout",
     "logout" : "logout",
     "register" : "register",
     "register" : "register",
+    "register_suggest" : "Do not have an account? Register",
     "able" : "able",
     "able" : "able",
-    "second": "second",
+    "second" : "second",
     "normal" : "normal",
     "normal" : "normal",
     "subscriber" : "registor",
     "subscriber" : "registor",
     "admin" : "admin",
     "admin" : "admin",
@@ -102,6 +104,17 @@
     "reload" : "reload",
     "reload" : "reload",
     "password" : "password",
     "password" : "password",
     "confirm" : "confirm",
     "confirm" : "confirm",
+    "connection" : "Connection",
+    "inter_error" : "Inter Error!",
+    "inter_error_detail" : "An internal error occurred during the verification process. Please check the details below.",
+    "ie_wrong_callback" : "The SNS service provider has sent an invalid value to the callback route. <a href=\"/login\">Try login again.</a>",
+    "oauth_conn_done" : " connected",
+    "oauth_conn_not" : " not found",
+    "oauth_conn_new" : "Connect..",
+    "oauth_signin_facebook" : "Sign in with facebook",
+    "oauth_signin_naver" : "Sign in with NAVER",
+    "oauth_settings_not_found" : "Administrator has not provided any data about using this feature.",
+    "oauth_disabled" : "Administrator has disabled this feature.",
 
 
     "user_head_warring" : "user's head will deleted if you close the browser or when you are editting as guest",
     "user_head_warring" : "user's head will deleted if you close the browser or when you are editting as guest",
     "http_warring" : "warning : if you are not on https connection, your information can be leaked. we won't response to that.",
     "http_warring" : "warning : if you are not on https connection, your information can be leaked. we won't response to that.",

+ 126 - 112
language/ko-KR.json

@@ -1,129 +1,143 @@
 {
 {
-    "edit": "편집",
+    "edit" : "편집",
     "raw" : "원본",
     "raw" : "원본",
-    "history": "역사",
-    "easy": "간단",
-    "skin": "스킨",
-    "delete": "삭제",
+    "history" : "역사",
+    "easy" : "간단",
+    "skin" : "스킨",
+    "delete" : "삭제",
     "regex" : "정규표현식",
     "regex" : "정규표현식",
     "language" : "언어",
     "language" : "언어",
-    "server": "서버",
-    "filter": "필터",
-    "move": "이동",
-    "hide": "숨김",
-    "list": "목록",
+    "server" : "서버",
+    "filter" : "필터",
+    "move" : "이동",
+    "hide" : "숨김",
+    "list" : "목록",
     "id" : "아이디",
     "id" : "아이디",
-    "revert": "되돌리기",
-    "version": "판",
-    "normal_version": "버전",
-    "document": "문서",
-    "all": "모든",
-    "ban": "차단",
-    "release": "해제",
-    "save": "저장",
-    "other": "기타",
-    "tool": "도구",
-    "plus": "추가",
-    "open": "열기",
-    "search": "검색",
-    "user": "사용자",
-    "alarm": "알림",
-    "watchlist": "주시 문서",
-    "recent": "최근",
-    "recent_changes": "최근 변경",
-    "discussion": "토론",
-    "login": "로그인",
-    "logout": "로그아웃",
-    "register": "회원가입",
-    "able": "가능",
-    "second": "초",
-    "normal": "일반",
-    "subscriber": "가입자",
-    "admin": "관리자",
-    "owner": "소유자",
-    "admin_group": "관리 그룹",
-    "user_head_warring": "비 로그인의 경우에는 사용자 head가 로그인하거나 브라우저 닫으면 날아갑니다.",
-    "http_warring": "주의 : 만약 https 연결이 아닌 경우 데이터가 유출될 가능성이 있습니다. 이에 대해 책임지지 않습니다.",
-    "new": "새",
-    "need": "필요한",
-    "upload": "파일 올리기",
-    "record": "기록",
-    "name": "이름",
-    "license": "라이선스",
+    "revert" : "되돌리기",
+    "version" : "판",
+    "normal_version" : "버전",
+    "document" : "문서",
+    "all" : "모든",
+    "ban" : "차단",
+    "release" : "해제",
+    "save" : "저장",
+    "other" : "기타",
+    "tool" : "도구",
+    "plus" : "추가",
+    "open" : "열기",
+    "search" : "검색",
+    "user" : "사용자",
+    "alarm" : "알림",
+    "watchlist" : "주시 문서",
+    "recent" : "최근",
+    "recent_changes" : "최근 변경",
+    "discussion" : "토론",
+    "login" : "로그인",
+    "logout" : "로그아웃",
+    "register" : "회원가입",
+    "register_suggest" : "계정이 없으신가요? 회원가입",
+    "able" : "가능",
+    "second" : "초",
+    "normal" : "일반",
+    "subscriber" : "가입자",
+    "admin" : "관리자",
+    "owner" : "소유자",
+    "admin_group" : "관리 그룹",
+    "user_head_warring" : "비 로그인의 경우에는 사용자 head가 로그인하거나 브라우저 닫으면 날아갑니다.",
+    "http_warring" : "주의 : 만약 https 연결이 아닌 경우 데이터가 유출될 가능성이 있습니다. 이에 대해 책임지지 않습니다.",
+    "new" : "새",
+    "need" : "필요한",
+    "upload" : "파일 올리기",
+    "record" : "기록",
+    "name" : "이름",
+    "license" : "라이선스",
     "bottom" : "하단",
     "bottom" : "하단",
     "text" : "문구",
     "text" : "문구",
-    "interwiki": "인터위키",
-    "update": "업데이트",
-    "setting": "설정",
-    "create": "생성",
-    "editor": "수정자",
-    "hour": "시간",
-    "time": "시각",
-    "close": "닫기",
-    "stop": "정지",
-    "restart": "재시작",
-    "agreement": "합의",
+    "interwiki" : "인터위키",
+    "update" : "업데이트",
+    "oauth_settings" : "OAuth 설정",
+    "setting" : "설정",
+    "create" : "생성",
+    "editor" : "수정자",
+    "hour" : "시간",
+    "time" : "시각",
+    "close" : "닫기",
+    "stop" : "정지",
+    "restart" : "재시작",
+    "agreement" : "합의",
     "load" : "불러오기",
     "load" : "불러오기",
-    "backlink": "역링크",
-    "why": "사유",
-    "random": "무작위",
-    "authority": "권한",
-    "file": "파일",
-    "change": "변경",
-    "compare": "비교",
-    "count": "횟수",
-    "check": "검사",
+    "backlink" : "역링크",
+    "why" : "사유",
+    "random" : "무작위",
+    "authority" : "권한",
+    "file" : "파일",
+    "change" : "변경",
+    "compare" : "비교",
+    "count" : "횟수",
+    "check" : "검사",
     "view" : "보기",
     "view" : "보기",
-    "preview": "미리보기",
-    "next": "다음",
-    "previous": "이전",
-    "no_login_warring": "비 로그인 상태로 진행 시 ip가 기록될 수 있습니다.",
-    "state": "상태",
-    "limitless": "무기한",
-    "period": "기간",
-    "now": "현재",
-    "blocked": "차단자",
-    "band": "대역",
-    "notice": "공지",
-    "writer": "작성자",
-    "upper": "상위",
-    "under": "하위",
-    "pass": "통과",
-    "category": "분류",
+    "preview" : "미리보기",
+    "next" : "다음",
+    "previous" : "이전",
+    "no_login_warring" : "비 로그인 상태로 진행 시 ip가 기록될 수 있습니다.",
+    "state" : "상태",
+    "limitless" : "무기한",
+    "period" : "기간",
+    "now" : "현재",
+    "blocked" : "차단자",
+    "band" : "대역",
+    "notice" : "공지",
+    "writer" : "작성자",
+    "upper" : "상위",
+    "under" : "하위",
+    "pass" : "통과",
+    "category" : "분류",
     "send" : "전송",
     "send" : "전송",
     "reload" : "새로고침",
     "reload" : "새로고침",
     "password" : "암호",
     "password" : "암호",
     "confirm" : "확인",
     "confirm" : "확인",
-    "authority_error": "권한이  부족합니다.",
-    "no_login_error": "비 로그인 상태 입니다.",
-    "no_exist_user_error": "계정이 없습니다.",
-    "no_admin_block_error": "관리자는 차단, 검사 할 수 없습니다.",
+    "connection" : "연결",
+    "inter_error" : "내부 오류!",
+    "inter_error_detail" : "인증 절차를 진행하는 도중 내부 오류가 발생했습니다. 아래에서 자세한 사항을 확인하세요.",
+    "ie_wrong_callback" : "SNS 서비스 제공자가 callback 라우트에 잘못된 값을 전송했습니다. <a href=\"/login\">다시 시도하세요.</a>",
+    "oauth_conn_done" : "됨",
+    "oauth_conn_not" : "없음",
+    "oauth_conn_new" : "연결하기",
+    "oauth_signin_facebook" : "Facebook으로 로그인",
+    "oauth_signin_naver" : "네이버 아이디로 로그인",
+    "oauth_settings_not_found" : "관리자가 이 기능을 사용하는데 대한 정보를 제공하지 않았습니다.",
+    "oauth_disabled" : "관리자가 이 기능을 비활성화시켰습니다.",
+
+    "authority_error" : "권한이  부족합니다.",
+    "no_login_error" : "비 로그인 상태 입니다.",
+    "no_exist_user_error" : "계정이 없습니다.",
+    "no_admin_block_error" : "관리자는 차단, 검사 할 수 없습니다.",
     "skin_error" : "이 스킨은 스킨 설정을 지원하지 않습니다.",
     "skin_error" : "이 스킨은 스킨 설정을 지원하지 않습니다.",
-    "same_id_exist_error": "동일한 아이디의 사용자가 있습니다.",
-    "long_id_error": "아이디는 20글자보다 짧아야 합니다.",
-    "id_char_error": "아이디에는 한글과 알파벳과 공백만 허용 됩니다.",
-    "file_exist_error": "파일이 없습니다.",
-    "password_error": "비밀번호가 다릅니다.",
-    "recaptcha_error": "리캡차를 통과하세요.",
-    "file_extension_error": "jpg, gif, jpeg, png, webp만 가능 합니다.",
-    "edit_record_error": "편집 기록은 500자를 넘을 수 없습니다.",
-    "same_file_error": "동일한 이름의 파일이 있습니다.",
-    "file_capacity_error": "파일 최대 용량 (mb) :",
-    "decument_exist_error": "내용이 원래 문서와 동일 합니다.",
-    "password_diffrent_error": "재 확인 비밀번호와 입력 비밀번호가 다릅니다.",
-    "edit_filter_error": "편집 필터에 의해 검열 되었습니다.",
-    "file_name_error": "파일 이름은 알파벳, 한글, 띄어쓰기, 언더바,  빼기표만 허용 됩니다.",
-    "template": "틀",
-    "out": "외부",
-    "logo": "로고",
-    "frontpage": "대문",
-    "max_file_size": "최대 파일 크기",
-    "backup_interval": "백업 간격",
-    "default": "기본",
-    "port": "포트",
-    "secret_key": "비밀키",
-    "update_branch": "업데이트 브랜치",
-    "main": "메인",
+    "same_id_exist_error" : "동일한 아이디의 사용자가 있습니다.",
+    "long_id_error" : "아이디는 20글자보다 짧아야 합니다.",
+    "id_char_error" : "아이디에는 한글과 알파벳과 공백만 허용 됩니다.",
+    "file_exist_error" : "파일이 없습니다.",
+    "password_error" : "비밀번호가 다릅니다.",
+    "recaptcha_error" : "리캡차를 통과하세요.",
+    "file_extension_error" : "jpg, gif, jpeg, png, webp만 가능 합니다.",
+    "edit_record_error" : "편집 기록은 500자를 넘을 수 없습니다.",
+    "same_file_error" : "동일한 이름의 파일이 있습니다.",
+    "file_capacity_error" : "파일 최대 용량 (mb) :",
+    "decument_exist_error" : "내용이 원래 문서와 동일 합니다.",
+    "password_diffrent_error" : "재 확인 비밀번호와 입력 비밀번호가 다릅니다.",
+    "edit_filter_error" : "편집 필터에 의해 검열 되었습니다.",
+    "file_name_error" : "파일 이름은 알파벳, 한글, 띄어쓰기, 언더바,  빼기표만 허용 됩니다.",
+    "template" : "틀",
+    "out" : "외부",
+    "logo" : "로고",
+    "frontpage" : "대문",
+    "max_file_size" : "최대 파일 크기",
+    "backup_interval" : "백업 간격",
+    "default" : "기본",
+    "port" : "포트",
+    "secret_key" : "비밀키",
+    "update_branch" : "업데이트 브랜치",
+    "main" : "메인",
     "indexing" : "인덱싱",
     "indexing" : "인덱싱",
     "register_text" : "회원가입 문구",
     "register_text" : "회원가입 문구",
     "non_login_alert" : "비 로그인 경고문"
     "non_login_alert" : "비 로그인 경고문"

+ 16 - 0
oauthsettings.json

@@ -0,0 +1,16 @@
+{
+    "_README" : {
+        "en" : "To use the oauth login feature, you must set the 'publish_url' value to a domain address that includes the https protocol, and actually support https connections.",
+        "ko" : "oauth 로그인 기능을 사용하려면 'publish_url'값을 https 프로토콜을 포함한 도메인 주소로 설정하고, 실제로 https 연결을 지원해야합니다.",
+        "support" : ["facebook", "naver"]
+    },
+    "publish_url" : "https://",
+    "facebook" : {
+        "client_id" : "",
+        "client_secret" : ""
+    },
+    "naver" : {
+        "client_id" : "",
+        "client_secret" : ""
+    }
+}

+ 1 - 1
readme.md

@@ -12,7 +12,7 @@ opennamu is a Python-based wiki engine. You can use opennamu by installing Pytho
  * [Contribute](#contribute)
  * [Contribute](#contribute)
  * [License](#license)
  * [License](#license)
  * [Authors](#authors)
  * [Authors](#authors)
- * [Etc.](#etc.)
+ * [Etc.](#etc)
 
 
 # Getting Started
 # Getting Started
 
 

+ 50 - 0
views/oauth.css

@@ -0,0 +1,50 @@
+.oauth-wrapper {
+    display: inline-block;
+}
+.oauth-list {
+    list-style: none;
+    padding: 0;
+}
+.oauth-list li {
+    display: inline-block;
+}
+
+.oauth-btn {
+    position: relative;
+    margin-left: 20px;
+    border: none;
+    display: inline-block;
+    padding: 25px;
+    padding-left: 75px;
+    border-radius: 10px;
+    color: #fff;
+}
+.oauth-btn a {
+    position: relative;
+}
+.oauth-btn.oauth-btn-facebook {
+    background-color: #3b5998;
+}
+.oauth-btn.oauth-btn-naver {
+    background-color: #00d337;
+}
+
+.oauth-btn-logo {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 10px;
+    height: 10px;
+    margin: 20px;
+    margin-left: 25px;
+    padding: 10px;
+    background-size: cover;
+    background-position: center center;
+}
+
+.oauth-btn-logo.oauth-btn-facebook {
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNOSA4aC0zdjRoM3YxMmg1di0xMmgzLjY0MmwuMzU4LTRoLTR2LTEuNjY3YzAtLjk1NS4xOTItMS4zMzMgMS4xMTUtMS4zMzNoMi44ODV2LTVoLTMuODA4Yy0zLjU5NiAwLTUuMTkyIDEuNTgzLTUuMTkyIDQuNjE1djMuMzg1eiIvPjwvc3ZnPg==);
+}
+.oauth-btn-logo.oauth-btn-naver {
+    background-image : url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEzLDI3VjIyOS45NzJIOTEuNTRWMTI2LjcwNmw3NC44NDQsMTAzLjI4NUwyNDQsMjI5Ljk3MlYyNy4wMDlIMTY2LjM4NFYxMjguNDg2TDkyLjQ2NCwyNy4wMDlaIi8+PC9zdmc+)
+}