Преглед на файлове

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 inter(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:
     curs.execute('select data from other where name = "ver"')
@@ -124,7 +125,8 @@ if setup_tool != 0:
         'scan', 
         'acl', 
         'inter', 
-        'html_filter'
+        'html_filter',
+        'oauth_conn'
     ]
 
     create_data['data'] = ['title', 'data']
@@ -148,6 +150,7 @@ if setup_tool != 0:
     create_data['acl'] = ['title', 'dec', 'dis', 'view', 'why']
     create_data['inter'] = ['title', 'link']
     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 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>',
         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>')
 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="/restart">''' + load_lang('server') + ' ' + load_lang('restart') + '''</a></li>
                         <li><a href="/update">''' + load_lang('update') + '''</a></li>
+                        <li><a href="/oauth_settings">''' + load_lang('oauth_settings') + '''</a></li>
                     </ul>
                     ''',
             menu = [['other', load_lang('other')]]
@@ -2700,6 +2723,7 @@ def close_topic_list(name = None, tool = None):
         
 @app.route('/login', methods=['POST', 'GET'])
 def login():
+    print(flask.request.referrer)
     if custom()[2] != 0:
         return redirect('/user')
     
@@ -2744,7 +2768,14 @@ def login():
         conn.commit()
         
         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(),    
             imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
             data =  '''
@@ -2754,13 +2785,110 @@ def login():
                         <input placeholder="''' + load_lang('password') + '''" name="pw" type="password">
                         <hr class=\"main_hr\">
                         ''' + 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\">
                         <span>''' + load_lang('http_warring') + '''</span>
                     </form>
                     ''',
             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'])
 def change_password():
@@ -2824,7 +2952,7 @@ def change_password():
             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()
 
             for lang_data in support_language:
@@ -2835,6 +2963,17 @@ def change_password():
 
             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(),    
                 imp = [load_lang('user') + ' ' + load_lang('setting') + ' ' + load_lang('edit'), wiki_set(), custom(), other2([0, 0])],
                 data =  '''
@@ -2859,6 +2998,9 @@ def change_password():
                             <br>
                             <select name="lang">''' + div3 + '''</select>
                             <hr class=\"main_hr\">
+                            <span>OAuth ''' + load_lang('connection') + '''</span>
+                            ''' + oauth_content + '''
+                            <hr class=\"main_hr\">
                             <button type="submit">''' + load_lang('edit') + '''</button>
                             <hr class=\"main_hr\">
                             <span>''' + load_lang('http_warring') + '''</span>

+ 13 - 0
func.py

@@ -265,6 +265,19 @@ def load_lang(data, num = 2):
         else:
             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):
     if re.search('(\.|:)', data):
         return 1

+ 14 - 1
language/en-US.json

@@ -49,6 +49,7 @@
     "license" : "license",
     "interwiki" : "interwiki",
     "update" : "update",
+    "oauth_settings" : "OAuth Settings",
     "setting" : "set",
     "create" : "create",
     "editor" : "editor",
@@ -77,8 +78,9 @@
     "login" : "login",
     "logout" : "logout",
     "register" : "register",
+    "register_suggest" : "Do not have an account? Register",
     "able" : "able",
-    "second": "second",
+    "second" : "second",
     "normal" : "normal",
     "subscriber" : "registor",
     "admin" : "admin",
@@ -102,6 +104,17 @@
     "reload" : "reload",
     "password" : "password",
     "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",
     "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" : "원본",
-    "history": "역사",
-    "easy": "간단",
-    "skin": "스킨",
-    "delete": "삭제",
+    "history" : "역사",
+    "easy" : "간단",
+    "skin" : "스킨",
+    "delete" : "삭제",
     "regex" : "정규표현식",
     "language" : "언어",
-    "server": "서버",
-    "filter": "필터",
-    "move": "이동",
-    "hide": "숨김",
-    "list": "목록",
+    "server" : "서버",
+    "filter" : "필터",
+    "move" : "이동",
+    "hide" : "숨김",
+    "list" : "목록",
     "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" : "하단",
     "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" : "불러오기",
-    "backlink": "역링크",
-    "why": "사유",
-    "random": "무작위",
-    "authority": "권한",
-    "file": "파일",
-    "change": "변경",
-    "compare": "비교",
-    "count": "횟수",
-    "check": "검사",
+    "backlink" : "역링크",
+    "why" : "사유",
+    "random" : "무작위",
+    "authority" : "권한",
+    "file" : "파일",
+    "change" : "변경",
+    "compare" : "비교",
+    "count" : "횟수",
+    "check" : "검사",
     "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" : "전송",
     "reload" : "새로고침",
     "password" : "암호",
     "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" : "이 스킨은 스킨 설정을 지원하지 않습니다.",
-    "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" : "인덱싱",
     "register_text" : "회원가입 문구",
     "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)
  * [License](#license)
  * [Authors](#authors)
- * [Etc.](#etc.)
+ * [Etc.](#etc)
 
 # 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+)
+}