diff --git a/package-lock.json b/package-lock.json index 5c2cdf2..9be3781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1756,6 +1756,11 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" }, + "charenc": { + "version": "0.0.2", + "resolved": "http://registry.npm.taobao.org/charenc/download/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -2216,6 +2221,11 @@ "which": "1.3.1" } }, + "crypt": { + "version": "0.0.2", + "resolved": "http://registry.npm.taobao.org/crypt/download/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -6562,6 +6572,16 @@ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" }, + "md5": { + "version": "2.2.1", + "resolved": "http://registry.npm.taobao.org/md5/download/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "1.1.6" + } + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", diff --git a/package.json b/package.json index 073f7f4..3b3fdc4 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "copy-to-clipboard": "^3.0.8", "load-script": "^1.0.0", + "md5": "^2.2.1", "pressure": "^2.1.2", "react": "^16.4.2", "react-dom": "^16.4.2", diff --git a/src/App.js b/src/App.js index 3d465ff..474415b 100644 --- a/src/App.js +++ b/src/App.js @@ -5,6 +5,20 @@ import {Sidebar} from './Sidebar'; import {PressureHelper} from './PressureHelper'; import {TokenCtx} from './UserAction'; +function TokenDeprecatedAlert(props) { + if(!props.token || props.token.startsWith('isop_')) // noinspection JSConstructorReturnsPrimitive + return null; + else + return ( +
+
+

树洞已更换登录方式,您原来的登录状态已失效。

+

请按右上角的按钮,点“注销”,然后重新登录。

+
+
+ ); +} + class App extends Component { constructor(props) { super(props); @@ -62,14 +76,15 @@ class App extends Component { backgroundImage: 'url('+(localStorage['REPLACE_ERIRI_WITH_URL'] || 'static/eriri_bg.jpg')+')' }} /> - <div className="left-container"> - <TokenCtx.Consumer>{(token)=>( + <TokenCtx.Consumer>{(token)=>( + <div className="left-container"> + <TokenDeprecatedAlert token={token.value} /> <Flow key={this.state.flow_render_key} show_sidebar={this.show_sidebar_bound} mode={this.state.mode} search_text={this.state.search_text} token={token.value} /> - )}</TokenCtx.Consumer> - <br /> - </div> + <br /> + </div> + )}</TokenCtx.Consumer> <Sidebar do_close={()=>{ this.setState({ sidebar_content: null, diff --git a/src/Flows.js b/src/Flows.js index 799710f..7297b40 100644 --- a/src/Flows.js +++ b/src/Flows.js @@ -341,14 +341,16 @@ function FlowChunk(props) { {!!props.title && <TitleLine text={props.title} />} {props.list.map((info,ind)=>( <LazyLoad key={info.pid} offset={500} height="15em" once={true} > - {!!(props.deletion_detect && props.mode==='list' && ind && props.list[ind-1].pid-info.pid>1) && - <div className="flow-item-row"> - <div className="box box-tip flow-item box-danger"> - {props.list[ind-1].pid-info.pid-1} 条被删除 + <div> + {!!(props.deletion_detect && props.mode==='list' && ind && props.list[ind-1].pid-info.pid>1) && + <div className="flow-item-row"> + <div className="box box-tip flow-item box-danger"> + {props.list[ind-1].pid-info.pid-1} 条被删除 + </div> </div> - </div> - } - <FlowItemRow info={info} show_sidebar={props.show_sidebar} token={token} /> + } + <FlowItemRow info={info} show_sidebar={props.show_sidebar} token={token} /> + </div> </LazyLoad> ))} </div> diff --git a/src/UserAction.css b/src/UserAction.css index 7908ab2..e9d4cb0 100644 --- a/src/UserAction.css +++ b/src/UserAction.css @@ -1,4 +1,4 @@ -.login-form form p { +.login-form p { margin: 1em 0; text-align: center; } diff --git a/src/UserAction.js b/src/UserAction.js index 87c84a3..ce7a8dd 100644 --- a/src/UserAction.js +++ b/src/UserAction.js @@ -1,5 +1,6 @@ import React, {Component, PureComponent} from 'react'; import {SafeTextarea} from './Common'; +import md5 from 'md5'; import './UserAction.css'; @@ -8,6 +9,10 @@ const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http:// const MAX_IMG_PX=2000; const MAX_IMG_FILESIZE=256000; +const ISOP_APPKEY='0feb3a8a831e11e8933a0050568508a5'; +const ISOP_APPCODE='0fec960a831e11e8933a0050568508a5'; +const ISOP_SVCID='PERSON_BASE_INFO,STUDENT_SCORE,STUDENT_COURSE_TABLE,STUDENT_COURSE_TABLE_ROOM,CARD_BALANCE'; + export const TokenCtx=React.createContext({ value: null, set_value: ()=>{}, @@ -24,8 +29,25 @@ export class LoginForm extends Component { this.password_ref=React.createRef(); } - do_login(event,set_token) { - event.preventDefault(); + do_sendcode() { + if(this.state.loading_status==='loading') + return; + + let param= + 'user='+this.username_ref.current.value+ + '&svcId='+ISOP_SVCID+ + '&appKey='+ISOP_APPKEY+ + '×tamp='+(+new Date()); + + fetch( + 'https://isop.pku.edu.cn/svcpub/svc/oauth/validcode?'+param+ + '&msg='+md5(param+ISOP_APPCODE), + {mode: 'no-cors'} + ); + alert('短信验证码应该会发到您的手机上,请注意查收!'); + } + + do_login(set_token) { if(this.state.loading_status==='loading') return; @@ -33,8 +55,9 @@ export class LoginForm extends Component { loading_status: 'loading', }); let data=new URLSearchParams(); - data.append('uid', this.username_ref.current.value); - data.append('password', this.password_ref.current.value); + data.append('username', this.username_ref.current.value); + data.append('valid_code', this.password_ref.current.value); + data.append('isnewloginflow', 'true'); fetch(LOGIN_BASE+'/login.php?platform=hole_xmcp_ml', { method: 'POST', headers: { @@ -68,44 +91,43 @@ export class LoginForm extends Component { return ( <TokenCtx.Consumer>{(token)=> <div className="login-form box"> - <form onSubmit={(e)=>this.do_login(e,token.set_value)}> - {token.value ? + {token.value ? + <div> + <p> + <b>您已登录。</b> + <button type="button" onClick={()=>{token.set_value(null);}}>注销</button> + </p> <p> - <b>您已登录。</b>Token: <code>{token.value||'(null)'}</code> <br /> + Token: <code>{token.value||'(null)'}</code> <br /> 请勿泄露 Token,它代表您的登录状态,与您的账户唯一对应且泄露后无法重置 - </p> : - <p>登录后可以使用关注、回复等功能</p> - } - <p> - <label> - 学号: - <input ref={this.username_ref} type="tel" /> - </label> - </p> - <p> - <label> - 密码: - <input ref={this.password_ref} type="password" /> - </label> - </p> - <p> - {this.state.loading_status==='loading' ? - <button disabled="disbled"> - <span className="icon icon-loading" /> -  正在登录 - </button> : - <button type="submit"> - <span className="icon icon-login" /> -  登录 - </button> - } - <button type="button" onClick={()=>{token.set_value(null);}}>退出</button> - </p> - <p> - 您的密码会被发送到 PKU Helper 服务器 <br /> - 我们不会记录您的密码和个人信息 - </p> - </form> + </p> + </div> : + <p>登录后可以使用关注、回复等功能</p> + } + <p> + <label> +  学号  + <input ref={this.username_ref} type="tel" /> + </label> + <button type="button" disabled={this.state.loading_status==='loading'} + onClick={(e)=>this.do_sendcode()}> + 发送验证码 + </button> + </p> + <p> + <label> + 验证码  + <input ref={this.password_ref} type="tel" /> + </label> + <button type="button" disabled={this.state.loading_status==='loading'} + onClick={(e)=>this.do_login(token.set_value)}> + 登录 + </button> + </p> + <p> + 登录请求会被发送到北大统一验证接口和 PKU Helper 服务器 <br /> + 我们不会记录或使用您的登录信息 + </p> </div> }</TokenCtx.Consumer> ) diff --git a/src/flows_api.js b/src/flows_api.js index 542aabc..1903dcf 100644 --- a/src/flows_api.js +++ b/src/flows_api.js @@ -137,5 +137,5 @@ export const API={ } return json; }); - } + }, }; \ No newline at end of file