[{"data":1,"prerenderedAt":3992},["ShallowReactive",2],{"api-nav":3,"api-guide:\u002Fapi\u002Ftutorials\u002Fsend-student-to-panel":351,"api-spec:guide:\u002Fapi\u002Ftutorials\u002Fsend-student-to-panel":3972},[4,28,57,95,115,301,317,331],{"id":5,"title":6,"links":7,"groups":27},"start","Start here",[8,11,15,18,21,24],{"title":9,"to":10},"Overview","\u002Fapi",{"title":12,"to":13,"status":14},"Quickstart","\u002Fapi\u002Fquickstart","available",{"title":16,"to":17,"status":14},"Environments","\u002Fapi\u002Fenvironments",{"title":19,"to":20,"status":14},"Authentication","\u002Fapi\u002Fauthentication",{"title":22,"to":23,"status":14},"Organizations","\u002Fapi\u002Forganizations",{"title":25,"to":26,"status":14},"Permissions","\u002Fapi\u002Fpermissions",[],{"id":29,"title":30,"links":31,"groups":56},"concepts","Concepts",[32,35,38,41,44,47,50,53],{"title":33,"to":34,"status":14},"Requests and responses","\u002Fapi\u002Frequests-and-responses",{"title":36,"to":37,"status":14},"Identifiers","\u002Fapi\u002Fidentifiers",{"title":39,"to":40,"status":14},"Pagination","\u002Fapi\u002Fpagination",{"title":42,"to":43},"Errors","\u002Fapi\u002Ferrors",{"title":45,"to":46,"status":14},"Rate limits","\u002Fapi\u002Frate-limits",{"title":48,"to":49,"status":14},"Retries","\u002Fapi\u002Fretries-and-idempotency",{"title":51,"to":52,"status":14},"Security","\u002Fapi\u002Fsecurity",{"title":54,"to":55,"status":14},"Versioning","\u002Fapi\u002Fversioning",[],{"id":58,"title":59,"links":60,"groups":94},"resources","Guides",[61,64,67,70,73,76,79,82,85,88,91],{"title":62,"to":63,"status":14},"Students","\u002Fapi\u002Fguides\u002Fstudents",{"title":65,"to":66,"status":14},"Bulk registration","\u002Fapi\u002Fguides\u002Fbulk-registration",{"title":68,"to":69,"status":14},"Passwords","\u002Fapi\u002Fguides\u002Fpasswords",{"title":71,"to":72,"status":14},"Supervisors","\u002Fapi\u002Fguides\u002Fsupervisors",{"title":74,"to":75,"status":14},"Reference data","\u002Fapi\u002Fguides\u002Freference-data",{"title":77,"to":78,"status":14},"API account","\u002Fapi\u002Fguides\u002Fapi-account",{"title":80,"to":81,"status":14},"Sign-in links","\u002Fapi\u002Fguides\u002Fsign-in-links",{"title":83,"to":84,"status":14},"Exams","\u002Fapi\u002Fguides\u002Fexams",{"title":86,"to":87,"status":14},"Applications","\u002Fapi\u002Fguides\u002Fapplications",{"title":89,"to":90,"status":14},"Group challenges","\u002Fapi\u002Fguides\u002Fgroup-challenges",{"title":92,"to":93,"status":14},"Certificates and reports","\u002Fapi\u002Fguides\u002Fcertificates-and-reports",[],{"id":96,"title":97,"links":98,"groups":114},"tutorials","Tutorials",[99,102,105,108,111],{"title":100,"to":101,"status":14},"Register and apply","\u002Fapi\u002Ftutorials\u002Fregister-and-apply",{"title":103,"to":104,"status":14},"Send a student to the panel","\u002Fapi\u002Ftutorials\u002Fsend-student-to-panel",{"title":106,"to":107,"status":14},"Change an application","\u002Fapi\u002Ftutorials\u002Fchange-an-application",{"title":109,"to":110,"status":14},"Collect results","\u002Fapi\u002Ftutorials\u002Fcollect-results",{"title":112,"to":113,"status":14},"Token handling","\u002Fapi\u002Ftutorials\u002Ftoken-handling",[],{"id":116,"title":117,"links":118,"groups":125},"reference","Reference",[119,122],{"title":120,"to":121},"All endpoints","\u002Fapi\u002Freference",{"title":123,"to":124},"Sandbox console","\u002Fapi\u002Fconsole",[126,135,145,166,206,212,230,255,271],{"tag":127,"slug":128,"links":129},"Health","health",[130],{"title":131,"to":132,"method":133,"deprecated":134},"Check that the API is up","\u002Fapi\u002Freference\u002Fget-health","GET",false,{"tag":77,"slug":136,"links":137},"api-account",[138,142],{"title":139,"to":140,"method":141,"deprecated":134},"Revoke the token you send, before it expires","\u002Fapi\u002Freference\u002Frevoke-token","POST",{"title":143,"to":144,"method":133,"deprecated":134},"Fetch the API account your token belongs to","\u002Fapi\u002Freference\u002Fget-current-api-account",{"tag":74,"slug":146,"links":147},"reference-data",[148,151,154,157,160,163],{"title":149,"to":150,"method":133,"deprecated":134},"List the countries a student can be registered in","\u002Fapi\u002Freference\u002Flist-countries",{"title":152,"to":153,"method":133,"deprecated":134},"Fetch one country by its id","\u002Fapi\u002Freference\u002Fget-country",{"title":155,"to":156,"method":133,"deprecated":134},"List the grades a student can be registered with","\u002Fapi\u002Freference\u002Flist-grades",{"title":158,"to":159,"method":133,"deprecated":134},"Fetch one grade by its id","\u002Fapi\u002Freference\u002Fget-grade",{"title":161,"to":162,"method":133,"deprecated":134},"List the organizations and their ids","\u002Fapi\u002Freference\u002Flist-organizations",{"title":164,"to":165,"method":133,"deprecated":134},"Fetch one organization by its id","\u002Fapi\u002Freference\u002Fget-organization",{"tag":62,"slug":167,"links":168},"students",[169,172,175,178,181,184,187,191,194,197,200,203],{"title":170,"to":171,"method":133,"deprecated":134},"List your students","\u002Fapi\u002Freference\u002Flist-students",{"title":173,"to":174,"method":141,"deprecated":134},"Register a student","\u002Fapi\u002Freference\u002Fregister-student",{"title":176,"to":177,"method":141,"deprecated":134},"Check a registration without registering the student","\u002Fapi\u002Freference\u002Fcheck-student-registration",{"title":179,"to":180,"method":141,"deprecated":134},"Register many students at once","\u002Fapi\u002Freference\u002Fcreate-student-import",{"title":182,"to":183,"method":133,"deprecated":134},"Follow a batch of students you sent","\u002Fapi\u002Freference\u002Fget-student-import",{"title":185,"to":186,"method":133,"deprecated":134},"Fetch one of your students","\u002Fapi\u002Freference\u002Fget-student",{"title":188,"to":189,"method":190,"deprecated":134},"Update one of your students","\u002Fapi\u002Freference\u002Fupdate-student","PUT",{"title":192,"to":193,"method":190,"deprecated":134},"Set the sign-in password of one of your students","\u002Fapi\u002Freference\u002Fset-student-password",{"title":195,"to":196,"method":133,"deprecated":134},"List your students who can use this organization","\u002Fapi\u002Freference\u002Flist-org-students",{"title":198,"to":199,"method":133,"deprecated":134},"Fetch one of your students, if they can use this organization","\u002Fapi\u002Freference\u002Fget-org-student",{"title":201,"to":202,"method":190,"deprecated":134},"Update one of your students and give them access to this organization","\u002Fapi\u002Freference\u002Fupdate-org-student",{"title":204,"to":205,"method":190,"deprecated":134},"Link one of your students to a supervisor on this organization","\u002Fapi\u002Freference\u002Flink-student-supervisor",{"tag":80,"slug":207,"links":208},"sign-in-links",[209],{"title":210,"to":211,"method":141,"deprecated":134},"Create a single-use sign-in link for one of your students","\u002Fapi\u002Freference\u002Fcreate-signin-link",{"tag":83,"slug":213,"links":214},"exams",[215,218,221,224,227],{"title":216,"to":217,"method":133,"deprecated":134},"List the exams open for applications","\u002Fapi\u002Freference\u002Flist-exams",{"title":219,"to":220,"method":133,"deprecated":134},"List an organization’s exam categories","\u002Fapi\u002Freference\u002Flist-exam-categories",{"title":222,"to":223,"method":133,"deprecated":134},"Fetch one exam category","\u002Fapi\u002Freference\u002Fget-exam-category",{"title":225,"to":226,"method":133,"deprecated":134},"List the exams one of your students can apply to","\u002Fapi\u002Freference\u002Flist-available-exams",{"title":228,"to":229,"method":133,"deprecated":134},"Fetch one exam that is open for applications","\u002Fapi\u002Freference\u002Fget-exam",{"tag":86,"slug":231,"links":232},"applications",[233,236,239,242,245,248,251],{"title":234,"to":235,"method":133,"deprecated":134},"List your students’ applications in this organization","\u002Fapi\u002Freference\u002Flist-applications",{"title":237,"to":238,"method":141,"deprecated":134},"Enter one of your students for an exam","\u002Fapi\u002Freference\u002Fcreate-application",{"title":240,"to":241,"method":133,"deprecated":134},"List your students’ applications for one exam","\u002Fapi\u002Freference\u002Flist-exam-applications",{"title":243,"to":244,"method":133,"deprecated":134},"List one of your students’ applications in this organization","\u002Fapi\u002Freference\u002Flist-student-applications",{"title":246,"to":247,"method":133,"deprecated":134},"Fetch one of your students’ applications","\u002Fapi\u002Freference\u002Fget-application",{"title":249,"to":250,"method":190,"deprecated":134},"Move one of your students’ applications to another exam","\u002Fapi\u002Freference\u002Fmove-application",{"title":252,"to":253,"method":254,"deprecated":134},"Withdraw one of your students from an exam","\u002Fapi\u002Freference\u002Fdelete-application","DELETE",{"tag":256,"slug":257,"links":258},"Documents","documents",[259,262,265,268],{"title":260,"to":261,"method":133,"deprecated":134},"Download a certificate file","\u002Fapi\u002Freference\u002Fdownload-certificate",{"title":263,"to":264,"method":133,"deprecated":134},"List one of your students’ released certificates","\u002Fapi\u002Freference\u002Flist-student-certificates",{"title":266,"to":267,"method":133,"deprecated":134},"Download a result report file","\u002Fapi\u002Freference\u002Fdownload-report",{"title":269,"to":270,"method":133,"deprecated":134},"List one of your students’ released result reports","\u002Fapi\u002Freference\u002Flist-student-reports",{"tag":89,"slug":272,"links":273},"group-challenges",[274,277,280,283,286,289,292,295,298],{"title":275,"to":276,"method":133,"deprecated":134},"List the group challenges an organization runs","\u002Fapi\u002Freference\u002Flist-group-challenges",{"title":278,"to":279,"method":133,"deprecated":134},"Fetch one group challenge","\u002Fapi\u002Freference\u002Fget-group-challenge",{"title":281,"to":282,"method":133,"deprecated":134},"List the groups your students are in for a group challenge","\u002Fapi\u002Freference\u002Flist-group-challenge-groups",{"title":284,"to":285,"method":133,"deprecated":134},"Fetch one group, with its steps and files","\u002Fapi\u002Freference\u002Fget-group-challenge-group",{"title":287,"to":288,"method":133,"deprecated":134},"List what has happened in one group","\u002Fapi\u002Freference\u002Flist-group-challenge-activity",{"title":290,"to":291,"method":141,"deprecated":134},"Send a group’s finished work for one of your students","\u002Fapi\u002Freference\u002Fsubmit-group-challenge-work",{"title":293,"to":294,"method":141,"deprecated":134},"Submit one step of a group for one of your students","\u002Fapi\u002Freference\u002Fsubmit-group-challenge-step",{"title":296,"to":297,"method":133,"deprecated":134},"List your students’ eligibility and groups for a group challenge","\u002Fapi\u002Freference\u002Flist-group-challenge-students",{"title":299,"to":300,"method":133,"deprecated":134},"Fetch one of your students’ eligibility and group for a group challenge","\u002Fapi\u002Freference\u002Fget-group-challenge-student",{"id":302,"title":303,"links":304,"groups":316},"clients","Clients",[305,307,310,313],{"title":9,"to":306,"status":14},"\u002Fapi\u002Fclients",{"title":308,"to":309,"status":14},"Node.js","\u002Fapi\u002Fclients\u002Fnode",{"title":311,"to":312,"status":14},"PHP","\u002Fapi\u002Fclients\u002Fphp",{"title":314,"to":315,"status":14},"Build your own","\u002Fapi\u002Fclients\u002Fbuild-your-own",[],{"id":318,"title":319,"links":320,"groups":330},"agents","AI agents",[321,324,327],{"title":322,"to":323},"AI connections","\u002Fapi\u002Fmcp",{"title":325,"to":326},"What it can do","\u002Fapi\u002Fmcp\u002Ftools",{"title":328,"to":329},"Agent skills","\u002Fapi\u002Fskills",[],{"id":332,"title":333,"links":334,"groups":350},"help","Help",[335,338,341,344,347],{"title":336,"to":337,"status":14},"Glossary","\u002Fapi\u002Fglossary",{"title":339,"to":340,"status":14},"FAQ","\u002Fapi\u002Ffaq",{"title":342,"to":343,"status":14},"Troubleshooting","\u002Fapi\u002Ftroubleshooting",{"title":345,"to":346,"status":14},"Support","\u002Fapi\u002Fsupport",{"title":348,"to":349},"Changelog","\u002Fapi\u002Fchangelog",[],{"id":352,"title":353,"body":354,"description":3963,"extension":3964,"meta":3965,"navTitle":103,"navigation":1141,"operations":3966,"order":3968,"path":104,"section":96,"seo":3969,"status":14,"stem":3970,"__hash__":3971},"apiGuides\u002Fapi\u002Ftutorials\u002Fsend-student-to-panel.md","Send a student into the panel",{"type":355,"value":356,"toc":3947},"minimark",[357,366,369,374,464,471,475,511,514,550,564,568,664,668,756,866,881,886,892,1002,1013,1033,1068,1074,1078,1088,2153,2167,2171,2177,2364,3312,3319,3323,3500,3513,3517,3529,3539,3597,3707,3719,3725,3772,3808,3812,3845,3851,3855,3910,3914,3943],[358,359,360,361,365],"p",{},"Your students already sign in to your own site. In this tutorial you add an ",[362,363,364],"strong",{},"Open my exam panel","\nbutton that takes a student straight into an organization's panel, signed in as themselves, with no\npassword to remember. You build it twice, as an Express route and as a plain PHP script, and handle\nevery way the API can refuse.",[358,367,368],{},"The API provides a sign-in link. This tutorial covers the one safe way to hand that link to a student.",[370,371,373],"h2",{"id":372},"how-a-sign-in-link-behaves","How a sign-in link behaves",[375,376,377,390],"table",{},[378,379,380],"thead",{},[381,382,383,387],"tr",{},[384,385,386],"th",{},"Property",[384,388,389],{},"Value",[391,392,393,409,420,432,440,448,456],"tbody",{},[381,394,395,399],{},[396,397,398],"td",{},"Request",[396,400,401,405,406],{},[402,403,404],"code",{},"POST \u002Fv1\u002F\u003CorganizationId>\u002Fauth\u002Fsignin"," with ",[402,407,408],{},"{ \"studentId\": \"…\", \"redirect\": \"…\" }",[381,410,411,414],{},[396,412,413],{},"Permission",[396,415,416,419],{},[402,417,418],{},"auth\u002Fsignin"," on that organization",[381,421,422,425],{},[396,423,424],{},"Lifetime",[396,426,427,428,431],{},"120 seconds from the moment it is issued (",[402,429,430],{},"expiresIn"," in the response)",[381,433,434,437],{},[396,435,436],{},"Uses",[396,438,439],{},"One. The first request that opens it spends it",[381,441,442,445],{},[396,443,444],{},"Who it signs in",[396,446,447],{},"Whoever opens it, as the student",[381,449,450,453],{},[396,451,452],{},"What the student needs",[396,454,455],{},"Access to that organization. No password, and no confirmed email address",[381,457,458,461],{},[396,459,460],{},"First use",[396,462,463],{},"Creates the organization's own copy of the student",[358,465,466,467,470],{},"That last row matters beyond this button. Applications, supervisor links, certificates and reports on\nan organization all need the organization's copy of the student, which exists only after their first\nsign-in there. See ",[468,469,22],"a",{"href":23},".",[370,472,474],{"id":473},"the-pattern-mint-on-click-redirect-at-once","The pattern: mint on click, redirect at once",[476,477,478,484,491,494,508],"ol",{},[479,480,481,482,470],"li",{},"The student presses a button on your page. The button submits a form to your server with ",[402,483,141],{},[479,485,486,487,490],{},"Your server works out who the student is from ",[362,488,489],{},"your own session",", and looks up the core student id\nyou stored when you registered them.",[479,492,493],{},"Your server asks the API for a link.",[479,495,496,497,500,501,504,505,470],{},"Your server answers ",[402,498,499],{},"303 See Other"," with the link in ",[402,502,503],{},"Location",", and ",[402,506,507],{},"Cache-Control: no-store",[479,509,510],{},"The browser follows it at once. After a short pass through the Main Team sign-in address, the\nstudent lands in the organization's panel, signed in.",[358,512,513],{},"Why each part matters:",[515,516,517,523,534,540],"ul",{},[479,518,519,522],{},[362,520,521],{},"Mint on click, not on page load."," A link minted when your page renders is useless two minutes\nlater, and until then it sits in your HTML as a working credential.",[479,524,525,530,531,533],{},[362,526,527,529],{},[402,528,141],{},", not a plain link."," Browsers, chat and email link previews, and security scanners fetch\nplain links ahead of time. If one of them triggers your route, it mints a link and may spend it\nbefore the student arrives. A form sent with ",[402,532,141],{}," goes only when the student presses the button.",[479,535,536,539],{},[362,537,538],{},"Redirect, don't display."," A URL shown on screen gets copied into chats and emails, and any\npreview of it spends it.",[479,541,542,545,546,549],{},[362,543,544],{},"Never log, email or store the URL."," Anyone who opens it within the 120 seconds is signed in as the\nstudent. Log the ",[402,547,548],{},"request_id"," instead; it is all support needs.",[551,552,554],"callout",{"type":553},"security",[358,555,556,559,560,563],{},[362,557,558],{},"Take the student id from your session, never from the request."," If your route accepted\n",[402,561,562],{},"?studentId="," from the browser, any signed-in student could sign in as any other student you\nregistered. The routes below have no student id in their URL at all.",[370,565,567],{"id":566},"before-you-start","Before you start",[515,569,570,596,609,619,657],{},[479,571,572,575,576,578,579,582,583,586,587,590,591,593,594,470],{},[362,573,574],{},"Permissions:"," ",[402,577,418],{}," on each organization you send students to. To diagnose a refusal\n(below) you also need ",[402,580,581],{},"student\u002Fread"," on ",[402,584,585],{},"mto",", and to fix one, ",[402,588,589],{},"student\u002Fupdate"," on the organization\n(or on ",[402,592,585],{}," for the flat route). See ",[468,595,25],{"href":26},[479,597,598,601,602,605,606,470],{},[362,599,600],{},"The core student id"," for each of your users, stored when you registered them with\n",[402,603,604],{},"POST \u002Fv1\u002Fstudent",". See ",[468,607,100],{"href":608},"\u002Fapi\u002Ftutorials\u002Fregister-and-apply#step-4-register-the-student",[479,610,611,614,615,618],{},[362,612,613],{},"The organization ids",", from ",[402,616,617],{},"GET \u002Fv1\u002Forganization",". They never change, so load them once.",[479,620,621,575,624,627,628,631,632,635,636,639,640,643,644,405,647,650,651,653,654,470],{},[362,622,623],{},"The helper file",[402,625,626],{},"mainteam.mjs"," or ",[402,629,630],{},"mainteam.php"," from\n",[468,633,100],{"href":634},"\u002Fapi\u002Ftutorials\u002Fregister-and-apply#the-helper-file",". It signs tokens, retries a\n",[402,637,638],{},"429"," after ",[402,641,642],{},"Retry-After",", and turns refusals into an ",[402,645,646],{},"ApiError",[402,648,649],{},"status",", ",[402,652,402],{}," and\n",[402,655,656],{},"requestId",[479,658,659,660,663],{},"Node.js 20 or newer with Express (",[402,661,662],{},"npm install express","), or PHP 8.1 or newer with sessions.",[370,665,667],{"id":666},"the-request","The request",[669,670,676],"pre",{"className":671,"code":672,"filename":673,"language":674,"meta":675,"style":675},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast","curl -s -X POST https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F\u003CorganizationId>\u002Fauth\u002Fsignin \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"studentId\": \"66e6a3f5c1d2b30012a4f9c1\" }'\n","curl","bash","",[402,677,678,720,737,747],{"__ignoreMap":675},[679,680,683,686,690,693,697,700,704,707,711,714,717],"span",{"class":681,"line":682},"line",1,[679,684,673],{"class":685},"soyes",[679,687,689],{"class":688},"s-5SL"," -s",[679,691,692],{"class":688}," -X",[679,694,696],{"class":695},"sT6z2"," POST",[679,698,699],{"class":695}," https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F",[679,701,703],{"class":702},"sHUrx","\u003C",[679,705,706],{"class":695},"organizationI",[679,708,710],{"class":709},"suds8","d",[679,712,713],{"class":702},">",[679,715,716],{"class":695},"\u002Fauth\u002Fsignin",[679,718,719],{"class":702}," \\\n",[679,721,723,726,729,732,735],{"class":681,"line":722},2,[679,724,725],{"class":688},"  -H",[679,727,728],{"class":695}," \"Authorization: Bearer ",[679,730,731],{"class":709},"$TOKEN",[679,733,734],{"class":695},"\"",[679,736,719],{"class":702},[679,738,740,742,745],{"class":681,"line":739},3,[679,741,725],{"class":688},[679,743,744],{"class":695}," \"Content-Type: application\u002Fjson\"",[679,746,719],{"class":702},[679,748,750,753],{"class":681,"line":749},4,[679,751,752],{"class":688},"  -d",[679,754,755],{"class":695}," '{ \"studentId\": \"66e6a3f5c1d2b30012a4f9c1\" }'\n",[669,757,762],{"className":758,"code":759,"filename":760,"language":761,"meta":675,"style":675},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast","{\n  \"success\": true,\n  \"message\": \"Sign-in link generated successfully.\",\n  \"data\": {\n    \"url\": \"https:\u002F\u002Fauth.main-team.org\u002Fapi\u002Fuser\u002Foauth\u002Finvoke?accessToken=q8x2…&redirectUrl=…\",\n    \"organization\": \"stem\",\n    \"studentId\": \"66e6a3f5c1d2b30012a4f9c1\",\n    \"expiresIn\": 120\n  }\n}\n","Response 200","json",[402,763,764,769,784,796,804,817,830,843,854,860],{"__ignoreMap":675},[679,765,766],{"class":681,"line":682},[679,767,768],{"class":709},"{\n",[679,770,771,775,778,781],{"class":681,"line":722},[679,772,774],{"class":773},"sne4z","  \"success\"",[679,776,777],{"class":709},": ",[679,779,780],{"class":688},"true",[679,782,783],{"class":709},",\n",[679,785,786,789,791,794],{"class":681,"line":739},[679,787,788],{"class":773},"  \"message\"",[679,790,777],{"class":709},[679,792,793],{"class":695},"\"Sign-in link generated successfully.\"",[679,795,783],{"class":709},[679,797,798,801],{"class":681,"line":749},[679,799,800],{"class":773},"  \"data\"",[679,802,803],{"class":709},": {\n",[679,805,807,810,812,815],{"class":681,"line":806},5,[679,808,809],{"class":773},"    \"url\"",[679,811,777],{"class":709},[679,813,814],{"class":695},"\"https:\u002F\u002Fauth.main-team.org\u002Fapi\u002Fuser\u002Foauth\u002Finvoke?accessToken=q8x2…&redirectUrl=…\"",[679,816,783],{"class":709},[679,818,820,823,825,828],{"class":681,"line":819},6,[679,821,822],{"class":773},"    \"organization\"",[679,824,777],{"class":709},[679,826,827],{"class":695},"\"stem\"",[679,829,783],{"class":709},[679,831,833,836,838,841],{"class":681,"line":832},7,[679,834,835],{"class":773},"    \"studentId\"",[679,837,777],{"class":709},[679,839,840],{"class":695},"\"66e6a3f5c1d2b30012a4f9c1\"",[679,842,783],{"class":709},[679,844,846,849,851],{"class":681,"line":845},8,[679,847,848],{"class":773},"    \"expiresIn\"",[679,850,777],{"class":709},[679,852,853],{"class":688},"120\n",[679,855,857],{"class":681,"line":856},9,[679,858,859],{"class":709},"  }\n",[679,861,863],{"class":681,"line":862},10,[679,864,865],{"class":709},"}\n",[358,867,868,869,872,873,876,877,880],{},"Treat ",[402,870,871],{},"url"," as opaque: redirect to it exactly as it came, and never build or change one yourself.\n",[402,874,875],{},"organization"," is the organization's slug and ",[402,878,879],{},"studentId"," is the id you sent.",[882,883,885],"h3",{"id":884},"choosing-where-the-student-lands","Choosing where the student lands",[358,887,888,891],{},[402,889,890],{},"redirect"," is optional. Leave it out and the student lands on the organization's home page. To land\nsomewhere else in the organization's panel, send a path on that panel:",[375,893,894,905],{},[378,895,896],{},[381,897,898,902],{},[384,899,900],{},[402,901,890],{},[384,903,904],{},"Result",[391,906,907,918,928,938,947,959,969,982,992],{},[381,908,909,915],{},[396,910,911],{},[912,913,914],"em",{},"(absent)",[396,916,917],{},"The organization's home page",[381,919,920,925],{},[396,921,922],{},[402,923,924],{},"\u002F",[396,926,927],{},"The root of the organization's panel",[381,929,930,935],{},[396,931,932],{},[402,933,934],{},"\u002Fdashboard",[396,936,937],{},"Accepted",[381,939,940,945],{},[396,941,942],{},[402,943,944],{},"\u002Fexams?tab=upcoming",[396,946,937],{},[381,948,949,954],{},[396,950,951],{},[402,952,953],{},"dashboard",[396,955,956,957],{},"Refused: no leading ",[402,958,924],{},[381,960,961,966],{},[396,962,963],{},[402,964,965],{},"https:\u002F\u002Fexample.com\u002F",[396,967,968],{},"Refused: not a path",[381,970,971,976],{},[396,972,973],{},[402,974,975],{},"\u002F\u002Fexample.com",[396,977,978,979,981],{},"Refused: a second ",[402,980,924],{}," would leave the site",[381,983,984,989],{},[396,985,986],{},[402,987,988],{},"\u002F\\example.com",[396,990,991],{},"Refused: backslashes are not allowed anywhere",[381,993,994,999],{},[396,995,996],{},[402,997,998],{},"\u002Fmy exams",[396,1000,1001],{},"Refused: whitespace is not allowed",[358,1003,1004,1005,1008,1009,1012],{},"A refused value answers ",[402,1006,1007],{},"400 bad_request"," with\n",[402,1010,1011],{},"redirect must be a site-relative path starting with \"\u002F\" (e.g. \"\u002Fdashboard\")",". The rule keeps every\nlink inside the organization's own panel; a link can never send the student to another site.",[358,1014,1015,1018,1019,1024,1025,1028,1029,1032],{},[362,1016,1017],{},"Straight to a group challenge."," A group challenge's page is where the student's group uploads its\nwork. ",[468,1020,1021],{"href":300},[402,1022,1023],{},"getGroupChallengeStudent"," and the student list\ngive its path as ",[402,1026,1027],{},"panelPath",", with ",[402,1030,1031],{},"{userId}"," left in it for the link to fill. Send it as is:",[669,1034,1036],{"className":758,"code":1035,"language":761,"meta":675,"style":675},"{\n  \"studentId\": \"\u003CstudentId>\",\n  \"redirect\": \"\u002Fstadia\u002F{userId}\u002Fgroup-challenges\u002F6650a1b2c3d4e5f6a7b8c9eb\"\n}\n",[402,1037,1038,1042,1054,1064],{"__ignoreMap":675},[679,1039,1040],{"class":681,"line":682},[679,1041,768],{"class":709},[679,1043,1044,1047,1049,1052],{"class":681,"line":722},[679,1045,1046],{"class":773},"  \"studentId\"",[679,1048,777],{"class":709},[679,1050,1051],{"class":695},"\"\u003CstudentId>\"",[679,1053,783],{"class":709},[679,1055,1056,1059,1061],{"class":681,"line":739},[679,1057,1058],{"class":773},"  \"redirect\"",[679,1060,777],{"class":709},[679,1062,1063],{"class":695},"\"\u002Fstadia\u002F{userId}\u002Fgroup-challenges\u002F6650a1b2c3d4e5f6a7b8c9eb\"\n",[679,1065,1066],{"class":681,"line":749},[679,1067,865],{"class":709},[358,1069,1070,1071,470],{},"See ",[468,1072,89],{"href":1073},"\u002Fapi\u002Fguides\u002Fgroup-challenges#your-students",[370,1075,1077],{"id":1076},"the-express-route","The Express route",[358,1079,1080,1081,1083,1084,1087],{},"Save this next to ",[402,1082,626],{}," as ",[402,1085,1086],{},"server.mjs",". Replace the stand-in authentication with your own.",[669,1089,1093],{"className":1090,"code":1091,"filename":1086,"language":1092,"meta":675,"style":675},"language-js shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u002F\u002F server.mjs: an \"Open my exam panel\" button for your own students.\n\u002F\u002F Node.js 20 or newer, Express 4 or 5 (npm install express), and mainteam.mjs next to it.\nimport express from 'express';\nimport { api, ApiError } from '.\u002Fmainteam.mjs';\n\nconst app = express();\n\n\u002F\u002F Replace this with your real authentication. It must put the signed-in user\n\u002F\u002F on req.user, including the core student id you stored at registration.\napp.use((req, res, next) => {\n  req.user = { mainTeamStudentId: '66e6a3f5c1d2b30012a4f9c1' };\n  next();\n});\n\n\u002F\u002F Organization ids never change: load them once at start-up.\nconst organizationIds = new Map(); \u002F\u002F slug -> _id\n\nasync function loadOrganizations() {\n  const res = await api('GET', '\u002Forganization?limit=100');\n  for (const org of res.data) organizationIds.set(org.slug, org._id);\n}\n\napp.get('\u002F', (req, res) => {\n  res.type('html').send(`\u003C!doctype html>\n\u003Cform method=\"post\" action=\"\u002Fpanel\u002Fstem\">\n  \u003Cbutton type=\"submit\">Open my STEM exam panel\u003C\u002Fbutton>\n\u003C\u002Fform>`);\n});\n\napp.post('\u002Fpanel\u002F:slug', async (req, res) => {\n  const organizationId = organizationIds.get(req.params.slug);\n  if (!organizationId) return res.status(404).type('text').send('Unknown organization.');\n\n  \u002F\u002F From your session, never from the request.\n  const studentId = req.user.mainTeamStudentId;\n\n  try {\n    const link = await api('POST', `\u002F${organizationId}\u002Fauth\u002Fsignin`, { studentId });\n    res.set('Cache-Control', 'no-store');\n    return res.redirect(303, link.data.url); \u002F\u002F never log link.data.url\n  } catch (error) {\n    const message = await explainRefusal(error, studentId, req.params.slug);\n    const status = error instanceof ApiError && error.status === 403 ? 403 : 502;\n    return res.status(status).type('text').send(message);\n  }\n});\n\n\u002F\u002F Turns a refusal into a message for the student, and logs what your staff need.\nasync function explainRefusal(error, studentId, slug) {\n  if (!(error instanceof ApiError)) {\n    console.error(`sign-in link failed: ${error.message}`);\n    return 'The exam panel cannot be reached right now. Please try again in a minute.';\n  }\n  console.error(`sign-in link refused: ${error.status} ${error.code} request ${error.requestId}`);\n\n  if (error.status === 403) {\n    \u002F\u002F Two causes share this status: the student has no access to this organization,\n    \u002F\u002F or this API account lacks auth\u002Fsignin there. The student's record tells them apart.\n    const student = (await api('GET', `\u002Fstudent\u002F${studentId}`).catch(() => null))?.data;\n    const platforms = student?.activatedPlatformsThisSeason ?? [];\n    if (student && !platforms.includes('common') && !platforms.includes(slug)) {\n      return 'You are not entered for this organization yet. Please contact your school.';\n    }\n    return 'The exam panel is not available from this site at the moment. We have been notified.';\n  }\n  if (error.status === 404) {\n    return 'We could not find your exam account. Please contact your school.';\n  }\n  return 'The exam panel cannot be reached right now. Please try again in a minute.';\n}\n\nawait loadOrganizations();\napp.listen(3000, () => console.log('Listening on port 3000'));\n","js",[402,1094,1095,1101,1106,1123,1137,1143,1161,1165,1170,1175,1208,1226,1234,1240,1245,1251,1273,1278,1293,1324,1350,1355,1360,1388,1413,1419,1425,1433,1438,1443,1474,1492,1539,1544,1550,1563,1568,1576,1610,1630,1651,1663,1681,1725,1750,1755,1760,1765,1771,1797,1816,1841,1851,1856,1897,1902,1916,1922,1928,1973,1992,2031,2042,2048,2058,2063,2077,2087,2092,2102,2107,2112,2121],{"__ignoreMap":675},[679,1096,1097],{"class":681,"line":682},[679,1098,1100],{"class":1099},"sLBg1","\u002F\u002F server.mjs: an \"Open my exam panel\" button for your own students.\n",[679,1102,1103],{"class":681,"line":722},[679,1104,1105],{"class":1099},"\u002F\u002F Node.js 20 or newer, Express 4 or 5 (npm install express), and mainteam.mjs next to it.\n",[679,1107,1108,1111,1114,1117,1120],{"class":681,"line":739},[679,1109,1110],{"class":702},"import",[679,1112,1113],{"class":709}," express ",[679,1115,1116],{"class":702},"from",[679,1118,1119],{"class":695}," 'express'",[679,1121,1122],{"class":709},";\n",[679,1124,1125,1127,1130,1132,1135],{"class":681,"line":749},[679,1126,1110],{"class":702},[679,1128,1129],{"class":709}," { api, ApiError } ",[679,1131,1116],{"class":702},[679,1133,1134],{"class":695}," '.\u002Fmainteam.mjs'",[679,1136,1122],{"class":709},[679,1138,1139],{"class":681,"line":806},[679,1140,1142],{"emptyLinePlaceholder":1141},true,"\n",[679,1144,1145,1148,1151,1154,1158],{"class":681,"line":819},[679,1146,1147],{"class":702},"const",[679,1149,1150],{"class":688}," app",[679,1152,1153],{"class":702}," =",[679,1155,1157],{"class":1156},"sKwhi"," express",[679,1159,1160],{"class":709},"();\n",[679,1162,1163],{"class":681,"line":832},[679,1164,1142],{"emptyLinePlaceholder":1141},[679,1166,1167],{"class":681,"line":845},[679,1168,1169],{"class":1099},"\u002F\u002F Replace this with your real authentication. It must put the signed-in user\n",[679,1171,1172],{"class":681,"line":856},[679,1173,1174],{"class":1099},"\u002F\u002F on req.user, including the core student id you stored at registration.\n",[679,1176,1177,1180,1183,1186,1189,1191,1194,1196,1199,1202,1205],{"class":681,"line":862},[679,1178,1179],{"class":709},"app.",[679,1181,1182],{"class":1156},"use",[679,1184,1185],{"class":709},"((",[679,1187,1188],{"class":685},"req",[679,1190,650],{"class":709},[679,1192,1193],{"class":685},"res",[679,1195,650],{"class":709},[679,1197,1198],{"class":685},"next",[679,1200,1201],{"class":709},") ",[679,1203,1204],{"class":702},"=>",[679,1206,1207],{"class":709}," {\n",[679,1209,1211,1214,1217,1220,1223],{"class":681,"line":1210},11,[679,1212,1213],{"class":709},"  req.user ",[679,1215,1216],{"class":702},"=",[679,1218,1219],{"class":709}," { mainTeamStudentId: ",[679,1221,1222],{"class":695},"'66e6a3f5c1d2b30012a4f9c1'",[679,1224,1225],{"class":709}," };\n",[679,1227,1229,1232],{"class":681,"line":1228},12,[679,1230,1231],{"class":1156},"  next",[679,1233,1160],{"class":709},[679,1235,1237],{"class":681,"line":1236},13,[679,1238,1239],{"class":709},"});\n",[679,1241,1243],{"class":681,"line":1242},14,[679,1244,1142],{"emptyLinePlaceholder":1141},[679,1246,1248],{"class":681,"line":1247},15,[679,1249,1250],{"class":1099},"\u002F\u002F Organization ids never change: load them once at start-up.\n",[679,1252,1254,1256,1259,1261,1264,1267,1270],{"class":681,"line":1253},16,[679,1255,1147],{"class":702},[679,1257,1258],{"class":688}," organizationIds",[679,1260,1153],{"class":702},[679,1262,1263],{"class":702}," new",[679,1265,1266],{"class":1156}," Map",[679,1268,1269],{"class":709},"(); ",[679,1271,1272],{"class":1099},"\u002F\u002F slug -> _id\n",[679,1274,1276],{"class":681,"line":1275},17,[679,1277,1142],{"emptyLinePlaceholder":1141},[679,1279,1281,1284,1287,1290],{"class":681,"line":1280},18,[679,1282,1283],{"class":702},"async",[679,1285,1286],{"class":702}," function",[679,1288,1289],{"class":1156}," loadOrganizations",[679,1291,1292],{"class":709},"() {\n",[679,1294,1296,1299,1302,1304,1307,1310,1313,1316,1318,1321],{"class":681,"line":1295},19,[679,1297,1298],{"class":702},"  const",[679,1300,1301],{"class":688}," res",[679,1303,1153],{"class":702},[679,1305,1306],{"class":702}," await",[679,1308,1309],{"class":1156}," api",[679,1311,1312],{"class":709},"(",[679,1314,1315],{"class":695},"'GET'",[679,1317,650],{"class":709},[679,1319,1320],{"class":695},"'\u002Forganization?limit=100'",[679,1322,1323],{"class":709},");\n",[679,1325,1327,1330,1333,1335,1338,1341,1344,1347],{"class":681,"line":1326},20,[679,1328,1329],{"class":702},"  for",[679,1331,1332],{"class":709}," (",[679,1334,1147],{"class":702},[679,1336,1337],{"class":688}," org",[679,1339,1340],{"class":702}," of",[679,1342,1343],{"class":709}," res.data) organizationIds.",[679,1345,1346],{"class":1156},"set",[679,1348,1349],{"class":709},"(org.slug, org._id);\n",[679,1351,1353],{"class":681,"line":1352},21,[679,1354,865],{"class":709},[679,1356,1358],{"class":681,"line":1357},22,[679,1359,1142],{"emptyLinePlaceholder":1141},[679,1361,1363,1365,1368,1370,1373,1376,1378,1380,1382,1384,1386],{"class":681,"line":1362},23,[679,1364,1179],{"class":709},[679,1366,1367],{"class":1156},"get",[679,1369,1312],{"class":709},[679,1371,1372],{"class":695},"'\u002F'",[679,1374,1375],{"class":709},", (",[679,1377,1188],{"class":685},[679,1379,650],{"class":709},[679,1381,1193],{"class":685},[679,1383,1201],{"class":709},[679,1385,1204],{"class":702},[679,1387,1207],{"class":709},[679,1389,1391,1394,1397,1399,1402,1405,1408,1410],{"class":681,"line":1390},24,[679,1392,1393],{"class":709},"  res.",[679,1395,1396],{"class":1156},"type",[679,1398,1312],{"class":709},[679,1400,1401],{"class":695},"'html'",[679,1403,1404],{"class":709},").",[679,1406,1407],{"class":1156},"send",[679,1409,1312],{"class":709},[679,1411,1412],{"class":695},"`\u003C!doctype html>\n",[679,1414,1416],{"class":681,"line":1415},25,[679,1417,1418],{"class":695},"\u003Cform method=\"post\" action=\"\u002Fpanel\u002Fstem\">\n",[679,1420,1422],{"class":681,"line":1421},26,[679,1423,1424],{"class":695},"  \u003Cbutton type=\"submit\">Open my STEM exam panel\u003C\u002Fbutton>\n",[679,1426,1428,1431],{"class":681,"line":1427},27,[679,1429,1430],{"class":695},"\u003C\u002Fform>`",[679,1432,1323],{"class":709},[679,1434,1436],{"class":681,"line":1435},28,[679,1437,1239],{"class":709},[679,1439,1441],{"class":681,"line":1440},29,[679,1442,1142],{"emptyLinePlaceholder":1141},[679,1444,1446,1448,1451,1453,1456,1458,1460,1462,1464,1466,1468,1470,1472],{"class":681,"line":1445},30,[679,1447,1179],{"class":709},[679,1449,1450],{"class":1156},"post",[679,1452,1312],{"class":709},[679,1454,1455],{"class":695},"'\u002Fpanel\u002F:slug'",[679,1457,650],{"class":709},[679,1459,1283],{"class":702},[679,1461,1332],{"class":709},[679,1463,1188],{"class":685},[679,1465,650],{"class":709},[679,1467,1193],{"class":685},[679,1469,1201],{"class":709},[679,1471,1204],{"class":702},[679,1473,1207],{"class":709},[679,1475,1477,1479,1482,1484,1487,1489],{"class":681,"line":1476},31,[679,1478,1298],{"class":702},[679,1480,1481],{"class":688}," organizationId",[679,1483,1153],{"class":702},[679,1485,1486],{"class":709}," organizationIds.",[679,1488,1367],{"class":1156},[679,1490,1491],{"class":709},"(req.params.slug);\n",[679,1493,1495,1498,1500,1503,1506,1509,1512,1514,1516,1519,1521,1523,1525,1528,1530,1532,1534,1537],{"class":681,"line":1494},32,[679,1496,1497],{"class":702},"  if",[679,1499,1332],{"class":709},[679,1501,1502],{"class":702},"!",[679,1504,1505],{"class":709},"organizationId) ",[679,1507,1508],{"class":702},"return",[679,1510,1511],{"class":709}," res.",[679,1513,649],{"class":1156},[679,1515,1312],{"class":709},[679,1517,1518],{"class":688},"404",[679,1520,1404],{"class":709},[679,1522,1396],{"class":1156},[679,1524,1312],{"class":709},[679,1526,1527],{"class":695},"'text'",[679,1529,1404],{"class":709},[679,1531,1407],{"class":1156},[679,1533,1312],{"class":709},[679,1535,1536],{"class":695},"'Unknown organization.'",[679,1538,1323],{"class":709},[679,1540,1542],{"class":681,"line":1541},33,[679,1543,1142],{"emptyLinePlaceholder":1141},[679,1545,1547],{"class":681,"line":1546},34,[679,1548,1549],{"class":1099},"  \u002F\u002F From your session, never from the request.\n",[679,1551,1553,1555,1558,1560],{"class":681,"line":1552},35,[679,1554,1298],{"class":702},[679,1556,1557],{"class":688}," studentId",[679,1559,1153],{"class":702},[679,1561,1562],{"class":709}," req.user.mainTeamStudentId;\n",[679,1564,1566],{"class":681,"line":1565},36,[679,1567,1142],{"emptyLinePlaceholder":1141},[679,1569,1571,1574],{"class":681,"line":1570},37,[679,1572,1573],{"class":702},"  try",[679,1575,1207],{"class":709},[679,1577,1579,1582,1585,1587,1589,1591,1593,1596,1598,1601,1604,1607],{"class":681,"line":1578},38,[679,1580,1581],{"class":702},"    const",[679,1583,1584],{"class":688}," link",[679,1586,1153],{"class":702},[679,1588,1306],{"class":702},[679,1590,1309],{"class":1156},[679,1592,1312],{"class":709},[679,1594,1595],{"class":695},"'POST'",[679,1597,650],{"class":709},[679,1599,1600],{"class":695},"`\u002F${",[679,1602,1603],{"class":709},"organizationId",[679,1605,1606],{"class":695},"}\u002Fauth\u002Fsignin`",[679,1608,1609],{"class":709},", { studentId });\n",[679,1611,1613,1616,1618,1620,1623,1625,1628],{"class":681,"line":1612},39,[679,1614,1615],{"class":709},"    res.",[679,1617,1346],{"class":1156},[679,1619,1312],{"class":709},[679,1621,1622],{"class":695},"'Cache-Control'",[679,1624,650],{"class":709},[679,1626,1627],{"class":695},"'no-store'",[679,1629,1323],{"class":709},[679,1631,1633,1636,1638,1640,1642,1645,1648],{"class":681,"line":1632},40,[679,1634,1635],{"class":702},"    return",[679,1637,1511],{"class":709},[679,1639,890],{"class":1156},[679,1641,1312],{"class":709},[679,1643,1644],{"class":688},"303",[679,1646,1647],{"class":709},", link.data.url); ",[679,1649,1650],{"class":1099},"\u002F\u002F never log link.data.url\n",[679,1652,1654,1657,1660],{"class":681,"line":1653},41,[679,1655,1656],{"class":709},"  } ",[679,1658,1659],{"class":702},"catch",[679,1661,1662],{"class":709}," (error) {\n",[679,1664,1666,1668,1671,1673,1675,1678],{"class":681,"line":1665},42,[679,1667,1581],{"class":702},[679,1669,1670],{"class":688}," message",[679,1672,1153],{"class":702},[679,1674,1306],{"class":702},[679,1676,1677],{"class":1156}," explainRefusal",[679,1679,1680],{"class":709},"(error, studentId, req.params.slug);\n",[679,1682,1684,1686,1689,1691,1694,1697,1700,1703,1706,1709,1712,1715,1717,1720,1723],{"class":681,"line":1683},43,[679,1685,1581],{"class":702},[679,1687,1688],{"class":688}," status",[679,1690,1153],{"class":702},[679,1692,1693],{"class":709}," error ",[679,1695,1696],{"class":702},"instanceof",[679,1698,1699],{"class":685}," ApiError",[679,1701,1702],{"class":702}," &&",[679,1704,1705],{"class":709}," error.status ",[679,1707,1708],{"class":702},"===",[679,1710,1711],{"class":688}," 403",[679,1713,1714],{"class":702}," ?",[679,1716,1711],{"class":688},[679,1718,1719],{"class":702}," :",[679,1721,1722],{"class":688}," 502",[679,1724,1122],{"class":709},[679,1726,1728,1730,1732,1734,1737,1739,1741,1743,1745,1747],{"class":681,"line":1727},44,[679,1729,1635],{"class":702},[679,1731,1511],{"class":709},[679,1733,649],{"class":1156},[679,1735,1736],{"class":709},"(status).",[679,1738,1396],{"class":1156},[679,1740,1312],{"class":709},[679,1742,1527],{"class":695},[679,1744,1404],{"class":709},[679,1746,1407],{"class":1156},[679,1748,1749],{"class":709},"(message);\n",[679,1751,1753],{"class":681,"line":1752},45,[679,1754,859],{"class":709},[679,1756,1758],{"class":681,"line":1757},46,[679,1759,1239],{"class":709},[679,1761,1763],{"class":681,"line":1762},47,[679,1764,1142],{"emptyLinePlaceholder":1141},[679,1766,1768],{"class":681,"line":1767},48,[679,1769,1770],{"class":1099},"\u002F\u002F Turns a refusal into a message for the student, and logs what your staff need.\n",[679,1772,1774,1776,1778,1780,1782,1785,1787,1789,1791,1794],{"class":681,"line":1773},49,[679,1775,1283],{"class":702},[679,1777,1286],{"class":702},[679,1779,1677],{"class":1156},[679,1781,1312],{"class":709},[679,1783,1784],{"class":685},"error",[679,1786,650],{"class":709},[679,1788,879],{"class":685},[679,1790,650],{"class":709},[679,1792,1793],{"class":685},"slug",[679,1795,1796],{"class":709},") {\n",[679,1798,1800,1802,1804,1806,1809,1811,1813],{"class":681,"line":1799},50,[679,1801,1497],{"class":702},[679,1803,1332],{"class":709},[679,1805,1502],{"class":702},[679,1807,1808],{"class":709},"(error ",[679,1810,1696],{"class":702},[679,1812,1699],{"class":685},[679,1814,1815],{"class":709},")) {\n",[679,1817,1819,1822,1824,1826,1829,1831,1833,1836,1839],{"class":681,"line":1818},51,[679,1820,1821],{"class":709},"    console.",[679,1823,1784],{"class":1156},[679,1825,1312],{"class":709},[679,1827,1828],{"class":695},"`sign-in link failed: ${",[679,1830,1784],{"class":709},[679,1832,470],{"class":695},[679,1834,1835],{"class":709},"message",[679,1837,1838],{"class":695},"}`",[679,1840,1323],{"class":709},[679,1842,1844,1846,1849],{"class":681,"line":1843},52,[679,1845,1635],{"class":702},[679,1847,1848],{"class":695}," 'The exam panel cannot be reached right now. Please try again in a minute.'",[679,1850,1122],{"class":709},[679,1852,1854],{"class":681,"line":1853},53,[679,1855,859],{"class":709},[679,1857,1859,1862,1864,1866,1869,1871,1873,1875,1878,1880,1882,1884,1887,1889,1891,1893,1895],{"class":681,"line":1858},54,[679,1860,1861],{"class":709},"  console.",[679,1863,1784],{"class":1156},[679,1865,1312],{"class":709},[679,1867,1868],{"class":695},"`sign-in link refused: ${",[679,1870,1784],{"class":709},[679,1872,470],{"class":695},[679,1874,649],{"class":709},[679,1876,1877],{"class":695},"} ${",[679,1879,1784],{"class":709},[679,1881,470],{"class":695},[679,1883,402],{"class":709},[679,1885,1886],{"class":695},"} request ${",[679,1888,1784],{"class":709},[679,1890,470],{"class":695},[679,1892,656],{"class":709},[679,1894,1838],{"class":695},[679,1896,1323],{"class":709},[679,1898,1900],{"class":681,"line":1899},55,[679,1901,1142],{"emptyLinePlaceholder":1141},[679,1903,1905,1907,1910,1912,1914],{"class":681,"line":1904},56,[679,1906,1497],{"class":702},[679,1908,1909],{"class":709}," (error.status ",[679,1911,1708],{"class":702},[679,1913,1711],{"class":688},[679,1915,1796],{"class":709},[679,1917,1919],{"class":681,"line":1918},57,[679,1920,1921],{"class":1099},"    \u002F\u002F Two causes share this status: the student has no access to this organization,\n",[679,1923,1925],{"class":681,"line":1924},58,[679,1926,1927],{"class":1099},"    \u002F\u002F or this API account lacks auth\u002Fsignin there. The student's record tells them apart.\n",[679,1929,1931,1933,1936,1938,1940,1943,1945,1947,1949,1951,1954,1956,1958,1960,1962,1965,1967,1970],{"class":681,"line":1930},59,[679,1932,1581],{"class":702},[679,1934,1935],{"class":688}," student",[679,1937,1153],{"class":702},[679,1939,1332],{"class":709},[679,1941,1942],{"class":702},"await",[679,1944,1309],{"class":1156},[679,1946,1312],{"class":709},[679,1948,1315],{"class":695},[679,1950,650],{"class":709},[679,1952,1953],{"class":695},"`\u002Fstudent\u002F${",[679,1955,879],{"class":709},[679,1957,1838],{"class":695},[679,1959,1404],{"class":709},[679,1961,1659],{"class":1156},[679,1963,1964],{"class":709},"(() ",[679,1966,1204],{"class":702},[679,1968,1969],{"class":688}," null",[679,1971,1972],{"class":709},"))?.data;\n",[679,1974,1976,1978,1981,1983,1986,1989],{"class":681,"line":1975},60,[679,1977,1581],{"class":702},[679,1979,1980],{"class":688}," platforms",[679,1982,1153],{"class":702},[679,1984,1985],{"class":709}," student?.activatedPlatformsThisSeason ",[679,1987,1988],{"class":702},"??",[679,1990,1991],{"class":709}," [];\n",[679,1993,1995,1998,2001,2004,2007,2010,2013,2015,2018,2020,2022,2024,2026,2028],{"class":681,"line":1994},61,[679,1996,1997],{"class":702},"    if",[679,1999,2000],{"class":709}," (student ",[679,2002,2003],{"class":702},"&&",[679,2005,2006],{"class":702}," !",[679,2008,2009],{"class":709},"platforms.",[679,2011,2012],{"class":1156},"includes",[679,2014,1312],{"class":709},[679,2016,2017],{"class":695},"'common'",[679,2019,1201],{"class":709},[679,2021,2003],{"class":702},[679,2023,2006],{"class":702},[679,2025,2009],{"class":709},[679,2027,2012],{"class":1156},[679,2029,2030],{"class":709},"(slug)) {\n",[679,2032,2034,2037,2040],{"class":681,"line":2033},62,[679,2035,2036],{"class":702},"      return",[679,2038,2039],{"class":695}," 'You are not entered for this organization yet. Please contact your school.'",[679,2041,1122],{"class":709},[679,2043,2045],{"class":681,"line":2044},63,[679,2046,2047],{"class":709},"    }\n",[679,2049,2051,2053,2056],{"class":681,"line":2050},64,[679,2052,1635],{"class":702},[679,2054,2055],{"class":695}," 'The exam panel is not available from this site at the moment. We have been notified.'",[679,2057,1122],{"class":709},[679,2059,2061],{"class":681,"line":2060},65,[679,2062,859],{"class":709},[679,2064,2066,2068,2070,2072,2075],{"class":681,"line":2065},66,[679,2067,1497],{"class":702},[679,2069,1909],{"class":709},[679,2071,1708],{"class":702},[679,2073,2074],{"class":688}," 404",[679,2076,1796],{"class":709},[679,2078,2080,2082,2085],{"class":681,"line":2079},67,[679,2081,1635],{"class":702},[679,2083,2084],{"class":695}," 'We could not find your exam account. Please contact your school.'",[679,2086,1122],{"class":709},[679,2088,2090],{"class":681,"line":2089},68,[679,2091,859],{"class":709},[679,2093,2095,2098,2100],{"class":681,"line":2094},69,[679,2096,2097],{"class":702},"  return",[679,2099,1848],{"class":695},[679,2101,1122],{"class":709},[679,2103,2105],{"class":681,"line":2104},70,[679,2106,865],{"class":709},[679,2108,2110],{"class":681,"line":2109},71,[679,2111,1142],{"emptyLinePlaceholder":1141},[679,2113,2115,2117,2119],{"class":681,"line":2114},72,[679,2116,1942],{"class":702},[679,2118,1289],{"class":1156},[679,2120,1160],{"class":709},[679,2122,2124,2126,2129,2131,2134,2137,2139,2142,2145,2147,2150],{"class":681,"line":2123},73,[679,2125,1179],{"class":709},[679,2127,2128],{"class":1156},"listen",[679,2130,1312],{"class":709},[679,2132,2133],{"class":688},"3000",[679,2135,2136],{"class":709},", () ",[679,2138,1204],{"class":702},[679,2140,2141],{"class":709}," console.",[679,2143,2144],{"class":1156},"log",[679,2146,1312],{"class":709},[679,2148,2149],{"class":695},"'Listening on port 3000'",[679,2151,2152],{"class":709},"));\n",[358,2154,2155,2156,2159,2160,2163,2164,2166],{},"Run it with ",[402,2157,2158],{},"node server.mjs",", open port 3000 of that machine in your browser and press the button. Your browser's\nnetwork tab shows one ",[402,2161,2162],{},"POST \u002Fpanel\u002Fstem"," answered ",[402,2165,1644],{},", then the sign-in hand-off, then the panel.",[370,2168,2170],{"id":2169},"the-plain-php-route","The plain PHP route",[358,2172,2173,2174,470],{},"Two files: the page with the button, and the script it posts to. Both assume your own login has\nalready put the student's core id in the session as ",[402,2175,2176],{},"mainteam_student_id",[669,2178,2183],{"className":2179,"code":2180,"filename":2181,"language":2182,"meta":675,"style":675},"language-php shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003C?php\n\u002F\u002F index.php: the page with the button. Your own login has already run.\nsession_start();\n$_SESSION['csrf'] ??= bin2hex(random_bytes(16));\n?>\n\u003C!doctype html>\n\u003Cform method=\"post\" action=\"\u002Fpanel.php?org=stem\">\n  \u003Cinput type=\"hidden\" name=\"csrf\" value=\"\u003C?= htmlspecialchars($_SESSION['csrf']) ?>\">\n  \u003Cbutton type=\"submit\">Open my STEM exam panel\u003C\u002Fbutton>\n\u003C\u002Fform>\n","index.php","php",[402,2184,2185,2193,2198,2205,2234,2239,2253,2278,2318,2356],{"__ignoreMap":675},[679,2186,2187,2190],{"class":681,"line":682},[679,2188,2189],{"class":702},"\u003C?",[679,2191,2192],{"class":688},"php\n",[679,2194,2195],{"class":681,"line":722},[679,2196,2197],{"class":1099},"\u002F\u002F index.php: the page with the button. Your own login has already run.\n",[679,2199,2200,2203],{"class":681,"line":739},[679,2201,2202],{"class":688},"session_start",[679,2204,1160],{"class":709},[679,2206,2207,2210,2213,2216,2219,2222,2224,2227,2229,2232],{"class":681,"line":749},[679,2208,2209],{"class":709},"$_SESSION[",[679,2211,2212],{"class":695},"'csrf'",[679,2214,2215],{"class":709},"] ",[679,2217,2218],{"class":702},"??=",[679,2220,2221],{"class":688}," bin2hex",[679,2223,1312],{"class":709},[679,2225,2226],{"class":1156},"random_bytes",[679,2228,1312],{"class":709},[679,2230,2231],{"class":688},"16",[679,2233,2152],{"class":709},[679,2235,2236],{"class":681,"line":806},[679,2237,2238],{"class":702},"?>\n",[679,2240,2241,2244,2247,2250],{"class":681,"line":819},[679,2242,2243],{"class":702},"\u003C!",[679,2245,2246],{"class":688},"doctype",[679,2248,2249],{"class":688}," html",[679,2251,2252],{"class":702},">\n",[679,2254,2255,2257,2260,2263,2265,2268,2271,2273,2276],{"class":681,"line":832},[679,2256,703],{"class":702},[679,2258,2259],{"class":688},"form",[679,2261,2262],{"class":688}," method",[679,2264,1216],{"class":702},[679,2266,2267],{"class":695},"\"post\"",[679,2269,2270],{"class":688}," action",[679,2272,1216],{"class":702},[679,2274,2275],{"class":695},"\"\u002Fpanel.php?org=stem\"",[679,2277,2252],{"class":702},[679,2279,2280,2283,2286,2289,2291,2294,2297,2299,2302,2305,2307,2310,2313,2316],{"class":681,"line":845},[679,2281,2282],{"class":702},"  \u003C",[679,2284,2285],{"class":688},"input",[679,2287,2288],{"class":688}," type",[679,2290,1216],{"class":702},[679,2292,2293],{"class":695},"\"hidden\"",[679,2295,2296],{"class":688}," name",[679,2298,1216],{"class":702},[679,2300,2301],{"class":695},"\"csrf\"",[679,2303,2304],{"class":688}," value",[679,2306,1216],{"class":702},[679,2308,2309],{"class":695},"\"\u003C?= htmlspecialchars(",[679,2311,2312],{"class":709},"$_SESSION",[679,2314,2315],{"class":695},"['csrf']) ?>\"",[679,2317,2252],{"class":702},[679,2319,2320,2322,2325,2327,2329,2332,2334,2337,2340,2343,2346,2349,2352,2354],{"class":681,"line":856},[679,2321,2282],{"class":702},[679,2323,2324],{"class":688},"button",[679,2326,2288],{"class":688},[679,2328,1216],{"class":702},[679,2330,2331],{"class":695},"\"submit\"",[679,2333,713],{"class":702},[679,2335,2336],{"class":688},"Open",[679,2338,2339],{"class":688}," my",[679,2341,2342],{"class":688}," STEM",[679,2344,2345],{"class":688}," exam",[679,2347,2348],{"class":688}," panel",[679,2350,2351],{"class":702},"\u003C\u002F",[679,2353,2324],{"class":688},[679,2355,2252],{"class":702},[679,2357,2358,2360,2362],{"class":681,"line":862},[679,2359,2351],{"class":702},[679,2361,2259],{"class":688},[679,2363,2252],{"class":702},[669,2365,2368],{"className":2179,"code":2366,"filename":2367,"language":2182,"meta":675,"style":675},"\u003C?php\n\u002F\u002F panel.php: POST \u002Fpanel.php?org=stem signs the current student into that organization's panel.\ndeclare(strict_types=1);\nrequire __DIR__ . '\u002Fmainteam.php';\n\nsession_start();\n\n\u002F\u002F Organization ids never change: keep the slug => _id map in your configuration.\nconst ORGANIZATION_IDS = [\n    'stem' => '64b7f1c2a9e3d45f10c2b7a1',\n];\n\nfunction fail(int $status, string $message): never\n{\n    http_response_code($status);\n    header('Content-Type: text\u002Fplain; charset=utf-8');\n    echo $message;\n    exit;\n}\n\nif ($_SERVER['REQUEST_METHOD'] !== 'POST') {\n    header('Allow: POST');\n    fail(405, 'Please use the button.');\n}\n$csrf = $_POST['csrf'] ?? null;\nif (!is_string($csrf) || !hash_equals($_SESSION['csrf'] ?? '', $csrf)) {\n    fail(400, 'Please reload the page and try again.');\n}\n\n\u002F\u002F From your session, never from the request.\n$studentId = $_SESSION['mainteam_student_id'] ?? null;\nif (!is_string($studentId)) {\n    fail(401, 'Please sign in first.');\n}\n\n$slug = (string) ($_GET['org'] ?? '');\n$organizationId = ORGANIZATION_IDS[$slug] ?? null;\nif ($organizationId === null) {\n    fail(404, 'Unknown organization.');\n}\n\n$api = MainTeam::fromEnv();\n\ntry {\n    $link = $api->call('POST', \"\u002F$organizationId\u002Fauth\u002Fsignin\", ['studentId' => $studentId]);\n    header('Cache-Control: no-store');\n    header('Location: ' . $link['data']['url'], true, 303); \u002F\u002F never log this URL\n    exit;\n} catch (ApiError $e) {\n    error_log(\"sign-in link refused: {$e->status} {$e->errorCode} request {$e->requestId}\");\n\n    if ($e->status === 403) {\n        \u002F\u002F Two causes share this status: the student has no access to this organization,\n        \u002F\u002F or this API account lacks auth\u002Fsignin there. The student's record tells them apart.\n        try {\n            $student = $api->call('GET', \"\u002Fstudent\u002F$studentId\")['data'] ?? null;\n        } catch (ApiError) {\n            $student = null;\n        }\n        $platforms = $student['activatedPlatformsThisSeason'] ?? [];\n        if ($student !== null && !in_array('common', $platforms, true) && !in_array($slug, $platforms, true)) {\n            fail(403, 'You are not entered for this organization yet. Please contact your school.');\n        }\n        fail(403, 'The exam panel is not available from this site at the moment. We have been notified.');\n    }\n    if ($e->status === 404) {\n        fail(404, 'We could not find your exam account. Please contact your school.');\n    }\n    fail(502, 'The exam panel cannot be reached right now. Please try again in a minute.');\n} catch (RuntimeException $e) {\n    error_log('sign-in link failed: ' . $e->getMessage());\n    fail(502, 'The exam panel cannot be reached right now. Please try again in a minute.');\n}\n","panel.php",[402,2369,2370,2376,2381,2398,2414,2418,2424,2428,2433,2445,2458,2463,2467,2495,2499,2507,2519,2527,2534,2538,2542,2563,2574,2591,2595,2615,2652,2668,2672,2676,2681,2702,2715,2731,2735,2739,2764,2782,2795,2809,2813,2817,2835,2839,2846,2888,2899,2937,2943,2957,2998,3002,3020,3025,3030,3037,3077,3090,3100,3105,3124,3167,3184,3188,3204,3208,3224,3239,3243,3259,3272,3294,3308],{"__ignoreMap":675},[679,2371,2372,2374],{"class":681,"line":682},[679,2373,2189],{"class":702},[679,2375,2192],{"class":688},[679,2377,2378],{"class":681,"line":722},[679,2379,2380],{"class":1099},"\u002F\u002F panel.php: POST \u002Fpanel.php?org=stem signs the current student into that organization's panel.\n",[679,2382,2383,2386,2388,2391,2393,2396],{"class":681,"line":739},[679,2384,2385],{"class":702},"declare",[679,2387,1312],{"class":709},[679,2389,2390],{"class":688},"strict_types",[679,2392,1216],{"class":702},[679,2394,2395],{"class":688},"1",[679,2397,1323],{"class":709},[679,2399,2400,2403,2406,2409,2412],{"class":681,"line":749},[679,2401,2402],{"class":702},"require",[679,2404,2405],{"class":688}," __DIR__",[679,2407,2408],{"class":702}," .",[679,2410,2411],{"class":695}," '\u002Fmainteam.php'",[679,2413,1122],{"class":709},[679,2415,2416],{"class":681,"line":806},[679,2417,1142],{"emptyLinePlaceholder":1141},[679,2419,2420,2422],{"class":681,"line":819},[679,2421,2202],{"class":688},[679,2423,1160],{"class":709},[679,2425,2426],{"class":681,"line":832},[679,2427,1142],{"emptyLinePlaceholder":1141},[679,2429,2430],{"class":681,"line":845},[679,2431,2432],{"class":1099},"\u002F\u002F Organization ids never change: keep the slug => _id map in your configuration.\n",[679,2434,2435,2437,2440,2442],{"class":681,"line":856},[679,2436,1147],{"class":702},[679,2438,2439],{"class":688}," ORGANIZATION_IDS",[679,2441,1153],{"class":702},[679,2443,2444],{"class":709}," [\n",[679,2446,2447,2450,2453,2456],{"class":681,"line":862},[679,2448,2449],{"class":695},"    'stem'",[679,2451,2452],{"class":702}," =>",[679,2454,2455],{"class":695}," '64b7f1c2a9e3d45f10c2b7a1'",[679,2457,783],{"class":709},[679,2459,2460],{"class":681,"line":1210},[679,2461,2462],{"class":709},"];\n",[679,2464,2465],{"class":681,"line":1228},[679,2466,1142],{"emptyLinePlaceholder":1141},[679,2468,2469,2472,2475,2477,2480,2483,2486,2489,2492],{"class":681,"line":1236},[679,2470,2471],{"class":702},"function",[679,2473,2474],{"class":1156}," fail",[679,2476,1312],{"class":709},[679,2478,2479],{"class":702},"int",[679,2481,2482],{"class":709}," $status, ",[679,2484,2485],{"class":702},"string",[679,2487,2488],{"class":709}," $message)",[679,2490,2491],{"class":702},":",[679,2493,2494],{"class":702}," never\n",[679,2496,2497],{"class":681,"line":1242},[679,2498,768],{"class":709},[679,2500,2501,2504],{"class":681,"line":1247},[679,2502,2503],{"class":688},"    http_response_code",[679,2505,2506],{"class":709},"($status);\n",[679,2508,2509,2512,2514,2517],{"class":681,"line":1253},[679,2510,2511],{"class":688},"    header",[679,2513,1312],{"class":709},[679,2515,2516],{"class":695},"'Content-Type: text\u002Fplain; charset=utf-8'",[679,2518,1323],{"class":709},[679,2520,2521,2524],{"class":681,"line":1275},[679,2522,2523],{"class":688},"    echo",[679,2525,2526],{"class":709}," $message;\n",[679,2528,2529,2532],{"class":681,"line":1280},[679,2530,2531],{"class":702},"    exit",[679,2533,1122],{"class":709},[679,2535,2536],{"class":681,"line":1295},[679,2537,865],{"class":709},[679,2539,2540],{"class":681,"line":1326},[679,2541,1142],{"emptyLinePlaceholder":1141},[679,2543,2544,2547,2550,2553,2555,2558,2561],{"class":681,"line":1352},[679,2545,2546],{"class":702},"if",[679,2548,2549],{"class":709}," ($_SERVER[",[679,2551,2552],{"class":695},"'REQUEST_METHOD'",[679,2554,2215],{"class":709},[679,2556,2557],{"class":702},"!==",[679,2559,2560],{"class":695}," 'POST'",[679,2562,1796],{"class":709},[679,2564,2565,2567,2569,2572],{"class":681,"line":1357},[679,2566,2511],{"class":688},[679,2568,1312],{"class":709},[679,2570,2571],{"class":695},"'Allow: POST'",[679,2573,1323],{"class":709},[679,2575,2576,2579,2581,2584,2586,2589],{"class":681,"line":1362},[679,2577,2578],{"class":1156},"    fail",[679,2580,1312],{"class":709},[679,2582,2583],{"class":688},"405",[679,2585,650],{"class":709},[679,2587,2588],{"class":695},"'Please use the button.'",[679,2590,1323],{"class":709},[679,2592,2593],{"class":681,"line":1390},[679,2594,865],{"class":709},[679,2596,2597,2600,2602,2605,2607,2609,2611,2613],{"class":681,"line":1415},[679,2598,2599],{"class":709},"$csrf ",[679,2601,1216],{"class":702},[679,2603,2604],{"class":709}," $_POST[",[679,2606,2212],{"class":695},[679,2608,2215],{"class":709},[679,2610,1988],{"class":702},[679,2612,1969],{"class":688},[679,2614,1122],{"class":709},[679,2616,2617,2619,2621,2623,2626,2629,2632,2634,2637,2640,2642,2644,2646,2649],{"class":681,"line":1421},[679,2618,2546],{"class":702},[679,2620,1332],{"class":709},[679,2622,1502],{"class":702},[679,2624,2625],{"class":688},"is_string",[679,2627,2628],{"class":709},"($csrf) ",[679,2630,2631],{"class":702},"||",[679,2633,2006],{"class":702},[679,2635,2636],{"class":688},"hash_equals",[679,2638,2639],{"class":709},"($_SESSION[",[679,2641,2212],{"class":695},[679,2643,2215],{"class":709},[679,2645,1988],{"class":702},[679,2647,2648],{"class":695}," ''",[679,2650,2651],{"class":709},", $csrf)) {\n",[679,2653,2654,2656,2658,2661,2663,2666],{"class":681,"line":1427},[679,2655,2578],{"class":1156},[679,2657,1312],{"class":709},[679,2659,2660],{"class":688},"400",[679,2662,650],{"class":709},[679,2664,2665],{"class":695},"'Please reload the page and try again.'",[679,2667,1323],{"class":709},[679,2669,2670],{"class":681,"line":1435},[679,2671,865],{"class":709},[679,2673,2674],{"class":681,"line":1440},[679,2675,1142],{"emptyLinePlaceholder":1141},[679,2677,2678],{"class":681,"line":1445},[679,2679,2680],{"class":1099},"\u002F\u002F From your session, never from the request.\n",[679,2682,2683,2686,2688,2691,2694,2696,2698,2700],{"class":681,"line":1476},[679,2684,2685],{"class":709},"$studentId ",[679,2687,1216],{"class":702},[679,2689,2690],{"class":709}," $_SESSION[",[679,2692,2693],{"class":695},"'mainteam_student_id'",[679,2695,2215],{"class":709},[679,2697,1988],{"class":702},[679,2699,1969],{"class":688},[679,2701,1122],{"class":709},[679,2703,2704,2706,2708,2710,2712],{"class":681,"line":1494},[679,2705,2546],{"class":702},[679,2707,1332],{"class":709},[679,2709,1502],{"class":702},[679,2711,2625],{"class":688},[679,2713,2714],{"class":709},"($studentId)) {\n",[679,2716,2717,2719,2721,2724,2726,2729],{"class":681,"line":1541},[679,2718,2578],{"class":1156},[679,2720,1312],{"class":709},[679,2722,2723],{"class":688},"401",[679,2725,650],{"class":709},[679,2727,2728],{"class":695},"'Please sign in first.'",[679,2730,1323],{"class":709},[679,2732,2733],{"class":681,"line":1546},[679,2734,865],{"class":709},[679,2736,2737],{"class":681,"line":1552},[679,2738,1142],{"emptyLinePlaceholder":1141},[679,2740,2741,2744,2746,2748,2750,2753,2756,2758,2760,2762],{"class":681,"line":1565},[679,2742,2743],{"class":709},"$slug ",[679,2745,1216],{"class":702},[679,2747,1332],{"class":709},[679,2749,2485],{"class":702},[679,2751,2752],{"class":709},") ($_GET[",[679,2754,2755],{"class":695},"'org'",[679,2757,2215],{"class":709},[679,2759,1988],{"class":702},[679,2761,2648],{"class":695},[679,2763,1323],{"class":709},[679,2765,2766,2769,2771,2773,2776,2778,2780],{"class":681,"line":1570},[679,2767,2768],{"class":709},"$organizationId ",[679,2770,1216],{"class":702},[679,2772,2439],{"class":688},[679,2774,2775],{"class":709},"[$slug] ",[679,2777,1988],{"class":702},[679,2779,1969],{"class":688},[679,2781,1122],{"class":709},[679,2783,2784,2786,2789,2791,2793],{"class":681,"line":1578},[679,2785,2546],{"class":702},[679,2787,2788],{"class":709}," ($organizationId ",[679,2790,1708],{"class":702},[679,2792,1969],{"class":688},[679,2794,1796],{"class":709},[679,2796,2797,2799,2801,2803,2805,2807],{"class":681,"line":1612},[679,2798,2578],{"class":1156},[679,2800,1312],{"class":709},[679,2802,1518],{"class":688},[679,2804,650],{"class":709},[679,2806,1536],{"class":695},[679,2808,1323],{"class":709},[679,2810,2811],{"class":681,"line":1632},[679,2812,865],{"class":709},[679,2814,2815],{"class":681,"line":1653},[679,2816,1142],{"emptyLinePlaceholder":1141},[679,2818,2819,2822,2824,2827,2830,2833],{"class":681,"line":1665},[679,2820,2821],{"class":709},"$api ",[679,2823,1216],{"class":702},[679,2825,2826],{"class":688}," MainTeam",[679,2828,2829],{"class":702},"::",[679,2831,2832],{"class":1156},"fromEnv",[679,2834,1160],{"class":709},[679,2836,2837],{"class":681,"line":1683},[679,2838,1142],{"emptyLinePlaceholder":1141},[679,2840,2841,2844],{"class":681,"line":1727},[679,2842,2843],{"class":702},"try",[679,2845,1207],{"class":709},[679,2847,2848,2851,2853,2856,2859,2862,2864,2866,2868,2871,2874,2877,2880,2883,2885],{"class":681,"line":1752},[679,2849,2850],{"class":709},"    $link ",[679,2852,1216],{"class":702},[679,2854,2855],{"class":709}," $api",[679,2857,2858],{"class":702},"->",[679,2860,2861],{"class":1156},"call",[679,2863,1312],{"class":709},[679,2865,1595],{"class":695},[679,2867,650],{"class":709},[679,2869,2870],{"class":695},"\"\u002F",[679,2872,2873],{"class":709},"$organizationId",[679,2875,2876],{"class":695},"\u002Fauth\u002Fsignin\"",[679,2878,2879],{"class":709},", [",[679,2881,2882],{"class":695},"'studentId'",[679,2884,2452],{"class":702},[679,2886,2887],{"class":709}," $studentId]);\n",[679,2889,2890,2892,2894,2897],{"class":681,"line":1757},[679,2891,2511],{"class":688},[679,2893,1312],{"class":709},[679,2895,2896],{"class":695},"'Cache-Control: no-store'",[679,2898,1323],{"class":709},[679,2900,2901,2903,2905,2908,2910,2913,2916,2919,2922,2925,2927,2929,2931,2934],{"class":681,"line":1762},[679,2902,2511],{"class":688},[679,2904,1312],{"class":709},[679,2906,2907],{"class":695},"'Location: '",[679,2909,2408],{"class":702},[679,2911,2912],{"class":709}," $link[",[679,2914,2915],{"class":695},"'data'",[679,2917,2918],{"class":709},"][",[679,2920,2921],{"class":695},"'url'",[679,2923,2924],{"class":709},"], ",[679,2926,780],{"class":688},[679,2928,650],{"class":709},[679,2930,1644],{"class":688},[679,2932,2933],{"class":709},"); ",[679,2935,2936],{"class":1099},"\u002F\u002F never log this URL\n",[679,2938,2939,2941],{"class":681,"line":1767},[679,2940,2531],{"class":702},[679,2942,1122],{"class":709},[679,2944,2945,2948,2950,2952,2954],{"class":681,"line":1773},[679,2946,2947],{"class":709},"} ",[679,2949,1659],{"class":702},[679,2951,1332],{"class":709},[679,2953,646],{"class":688},[679,2955,2956],{"class":709}," $e) {\n",[679,2958,2959,2962,2964,2967,2970,2972,2974,2977,2979,2981,2984,2987,2989,2991,2993,2996],{"class":681,"line":1799},[679,2960,2961],{"class":688},"    error_log",[679,2963,1312],{"class":709},[679,2965,2966],{"class":695},"\"sign-in link refused: {",[679,2968,2969],{"class":709},"$e",[679,2971,2858],{"class":702},[679,2973,649],{"class":709},[679,2975,2976],{"class":695},"} {",[679,2978,2969],{"class":709},[679,2980,2858],{"class":702},[679,2982,2983],{"class":709},"errorCode",[679,2985,2986],{"class":695},"} request {",[679,2988,2969],{"class":709},[679,2990,2858],{"class":702},[679,2992,656],{"class":709},[679,2994,2995],{"class":695},"}\"",[679,2997,1323],{"class":709},[679,2999,3000],{"class":681,"line":1818},[679,3001,1142],{"emptyLinePlaceholder":1141},[679,3003,3004,3006,3009,3011,3014,3016,3018],{"class":681,"line":1843},[679,3005,1997],{"class":702},[679,3007,3008],{"class":709}," ($e",[679,3010,2858],{"class":702},[679,3012,3013],{"class":709},"status ",[679,3015,1708],{"class":702},[679,3017,1711],{"class":688},[679,3019,1796],{"class":709},[679,3021,3022],{"class":681,"line":1853},[679,3023,3024],{"class":1099},"        \u002F\u002F Two causes share this status: the student has no access to this organization,\n",[679,3026,3027],{"class":681,"line":1858},[679,3028,3029],{"class":1099},"        \u002F\u002F or this API account lacks auth\u002Fsignin there. The student's record tells them apart.\n",[679,3031,3032,3035],{"class":681,"line":1899},[679,3033,3034],{"class":702},"        try",[679,3036,1207],{"class":709},[679,3038,3039,3042,3044,3046,3048,3050,3052,3054,3056,3059,3062,3064,3067,3069,3071,3073,3075],{"class":681,"line":1904},[679,3040,3041],{"class":709},"            $student ",[679,3043,1216],{"class":702},[679,3045,2855],{"class":709},[679,3047,2858],{"class":702},[679,3049,2861],{"class":1156},[679,3051,1312],{"class":709},[679,3053,1315],{"class":695},[679,3055,650],{"class":709},[679,3057,3058],{"class":695},"\"\u002Fstudent\u002F",[679,3060,3061],{"class":709},"$studentId",[679,3063,734],{"class":695},[679,3065,3066],{"class":709},")[",[679,3068,2915],{"class":695},[679,3070,2215],{"class":709},[679,3072,1988],{"class":702},[679,3074,1969],{"class":688},[679,3076,1122],{"class":709},[679,3078,3079,3082,3084,3086,3088],{"class":681,"line":1918},[679,3080,3081],{"class":709},"        } ",[679,3083,1659],{"class":702},[679,3085,1332],{"class":709},[679,3087,646],{"class":688},[679,3089,1796],{"class":709},[679,3091,3092,3094,3096,3098],{"class":681,"line":1924},[679,3093,3041],{"class":709},[679,3095,1216],{"class":702},[679,3097,1969],{"class":688},[679,3099,1122],{"class":709},[679,3101,3102],{"class":681,"line":1930},[679,3103,3104],{"class":709},"        }\n",[679,3106,3107,3110,3112,3115,3118,3120,3122],{"class":681,"line":1975},[679,3108,3109],{"class":709},"        $platforms ",[679,3111,1216],{"class":702},[679,3113,3114],{"class":709}," $student[",[679,3116,3117],{"class":695},"'activatedPlatformsThisSeason'",[679,3119,2215],{"class":709},[679,3121,1988],{"class":702},[679,3123,1991],{"class":709},[679,3125,3126,3129,3132,3134,3136,3138,3140,3143,3145,3147,3150,3152,3154,3156,3158,3160,3163,3165],{"class":681,"line":1994},[679,3127,3128],{"class":702},"        if",[679,3130,3131],{"class":709}," ($student ",[679,3133,2557],{"class":702},[679,3135,1969],{"class":688},[679,3137,1702],{"class":702},[679,3139,2006],{"class":702},[679,3141,3142],{"class":688},"in_array",[679,3144,1312],{"class":709},[679,3146,2017],{"class":695},[679,3148,3149],{"class":709},", $platforms, ",[679,3151,780],{"class":688},[679,3153,1201],{"class":709},[679,3155,2003],{"class":702},[679,3157,2006],{"class":702},[679,3159,3142],{"class":688},[679,3161,3162],{"class":709},"($slug, $platforms, ",[679,3164,780],{"class":688},[679,3166,1815],{"class":709},[679,3168,3169,3172,3174,3177,3179,3182],{"class":681,"line":2033},[679,3170,3171],{"class":1156},"            fail",[679,3173,1312],{"class":709},[679,3175,3176],{"class":688},"403",[679,3178,650],{"class":709},[679,3180,3181],{"class":695},"'You are not entered for this organization yet. Please contact your school.'",[679,3183,1323],{"class":709},[679,3185,3186],{"class":681,"line":2044},[679,3187,3104],{"class":709},[679,3189,3190,3193,3195,3197,3199,3202],{"class":681,"line":2050},[679,3191,3192],{"class":1156},"        fail",[679,3194,1312],{"class":709},[679,3196,3176],{"class":688},[679,3198,650],{"class":709},[679,3200,3201],{"class":695},"'The exam panel is not available from this site at the moment. We have been notified.'",[679,3203,1323],{"class":709},[679,3205,3206],{"class":681,"line":2060},[679,3207,2047],{"class":709},[679,3209,3210,3212,3214,3216,3218,3220,3222],{"class":681,"line":2065},[679,3211,1997],{"class":702},[679,3213,3008],{"class":709},[679,3215,2858],{"class":702},[679,3217,3013],{"class":709},[679,3219,1708],{"class":702},[679,3221,2074],{"class":688},[679,3223,1796],{"class":709},[679,3225,3226,3228,3230,3232,3234,3237],{"class":681,"line":2079},[679,3227,3192],{"class":1156},[679,3229,1312],{"class":709},[679,3231,1518],{"class":688},[679,3233,650],{"class":709},[679,3235,3236],{"class":695},"'We could not find your exam account. Please contact your school.'",[679,3238,1323],{"class":709},[679,3240,3241],{"class":681,"line":2089},[679,3242,2047],{"class":709},[679,3244,3245,3247,3249,3252,3254,3257],{"class":681,"line":2094},[679,3246,2578],{"class":1156},[679,3248,1312],{"class":709},[679,3250,3251],{"class":688},"502",[679,3253,650],{"class":709},[679,3255,3256],{"class":695},"'The exam panel cannot be reached right now. Please try again in a minute.'",[679,3258,1323],{"class":709},[679,3260,3261,3263,3265,3267,3270],{"class":681,"line":2104},[679,3262,2947],{"class":709},[679,3264,1659],{"class":702},[679,3266,1332],{"class":709},[679,3268,3269],{"class":688},"RuntimeException",[679,3271,2956],{"class":709},[679,3273,3274,3276,3278,3281,3283,3286,3288,3291],{"class":681,"line":2109},[679,3275,2961],{"class":688},[679,3277,1312],{"class":709},[679,3279,3280],{"class":695},"'sign-in link failed: '",[679,3282,2408],{"class":702},[679,3284,3285],{"class":709}," $e",[679,3287,2858],{"class":702},[679,3289,3290],{"class":1156},"getMessage",[679,3292,3293],{"class":709},"());\n",[679,3295,3296,3298,3300,3302,3304,3306],{"class":681,"line":2114},[679,3297,2578],{"class":1156},[679,3299,1312],{"class":709},[679,3301,3251],{"class":688},[679,3303,650],{"class":709},[679,3305,3256],{"class":695},[679,3307,1323],{"class":709},[679,3309,3310],{"class":681,"line":2123},[679,3311,865],{"class":709},[358,3313,3314,3315,3318],{},"The hidden ",[402,3316,3317],{},"csrf"," field is ordinary cross-site request protection for a form that acts on the\nsigned-in user. If your framework already protects forms, use its mechanism instead.",[370,3320,3322],{"id":3321},"when-the-link-is-refused","When the link is refused",[375,3324,3325,3341],{},[378,3326,3327],{},[381,3328,3329,3332,3335,3338],{},[384,3330,3331],{},"Status",[384,3333,3334],{},"Message",[384,3336,3337],{},"Cause",[384,3339,3340],{},"What to do",[391,3342,3343,3369,3389,3407,3424,3441,3461,3482],{},[381,3344,3345,3350,3355,3366],{},[396,3346,3347],{},[402,3348,3349],{},"403 forbidden",[396,3351,3352],{},[402,3353,3354],{},"Student is not activated for organization stem.",[396,3356,3357,3358,3361,3362,3365],{},"The student's ",[402,3359,3360],{},"activatedPlatformsThisSeason"," holds neither ",[402,3363,3364],{},"common"," nor this organization's slug",[396,3367,3368],{},"Grant access (below), then let the student press the button again",[381,3370,3371,3375,3380,3386],{},[396,3372,3373],{},[402,3374,3349],{},[396,3376,3377],{},[402,3378,3379],{},"Insufficient role permissions",[396,3381,3382,3383,3385],{},"Your account lacks ",[402,3384,418],{}," on this organization",[396,3387,3388],{},"Ask your operator for the role. Retrying does not help",[381,3390,3391,3396,3401,3404],{},[396,3392,3393],{},[402,3394,3395],{},"404 not_found",[396,3397,3398],{},[402,3399,3400],{},"Student not found!",[396,3402,3403],{},"The id is not one of your students. A student of another account, a mistyped id and a missing student all answer the same",[396,3405,3406],{},"Check the id stored against this user",[381,3408,3409,3413,3418,3421],{},[396,3410,3411],{},[402,3412,3395],{},[396,3414,3415],{},[402,3416,3417],{},"Organization not found!",[396,3419,3420],{},"The organization id in the path is wrong",[396,3422,3423],{},"Reload your organization ids",[381,3425,3426,3430,3435,3438],{},[396,3427,3428],{},[402,3429,1007],{},[396,3431,3432],{},[402,3433,3434],{},"studentId must be a mongodb id",[396,3436,3437],{},"The stored id is not a 24-character hex id",[396,3439,3440],{},"Fix the stored id",[381,3442,3443,3447,3452,3458],{},[396,3444,3445],{},[402,3446,1007],{},[396,3448,3449],{},[402,3450,3451],{},"redirect must be a site-relative path…",[396,3453,3454,3455,3457],{},"The ",[402,3456,890],{}," value breaks the rule above",[396,3459,3460],{},"Send a path, or leave it out",[381,3462,3463,3468,3473,3476],{},[396,3464,3465],{},[402,3466,3467],{},"429 too_many_requests",[396,3469,3470],{},[402,3471,3472],{},"Too many requests to this operation. Wait the number of seconds in Retry-After, then try again.",[396,3474,3475],{},"More than 100 links in 60 seconds across your account",[396,3477,3478,3479,3481],{},"Wait ",[402,3480,642],{}," seconds; the helper does this for you",[381,3483,3484,3489,3494,3497],{},[396,3485,3486],{},[402,3487,3488],{},"500 internal_error",[396,3490,3491],{},[402,3492,3493],{},"Could not issue a sign-in token, please retry.",[396,3495,3496],{},"A rare fault while issuing the link",[396,3498,3499],{},"Retry once",[358,3501,3502,3503,3505,3506,3509,3510,470],{},"Both ",[402,3504,3176],{}," answers carry the code ",[402,3507,3508],{},"forbidden",", so the routes above do not read the message to tell\nthem apart. They read the student instead. If the student's list of organizations lacks this one,\naccess is the problem. Otherwise it is your account's permission. See ",[468,3511,42],{"href":3512},"\u002Fapi\u002Ferrors#forbidden",[882,3514,3516],{"id":3515},"granting-access-to-an-organization","Granting access to an organization",[358,3518,3519,3521,3522,3524,3525,3528],{},[402,3520,3360],{}," lists the organizations a student may use: ",[402,3523,3364],{}," means every\norganization, and otherwise it holds organization slugs. A student you register without the field gets\n",[402,3526,3527],{},"[\"common\"]",", so you see this refusal only if you set the list yourself.",[358,3530,3531,3532,504,3535,3538],{},"The simplest fix adds one organization and touches nothing else: send the organization route an\nempty body. Here the student was on ",[402,3533,3534],{},"[\"neo\"]",[402,3536,3537],{},"\u003CorganizationId>"," is stem's:",[669,3540,3542],{"className":671,"code":3541,"filename":673,"language":674,"meta":675,"style":675},"curl -s -X PUT https:\u002F\u002Fapi.main-team.org\u002Fv1\u002F\u003CorganizationId>\u002Fstudent\u002F66e6a3f5c1d2b30012a4f9c1 \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{}'\n",[402,3543,3544,3570,3582,3590],{"__ignoreMap":675},[679,3545,3546,3548,3550,3552,3555,3557,3559,3561,3563,3565,3568],{"class":681,"line":682},[679,3547,673],{"class":685},[679,3549,689],{"class":688},[679,3551,692],{"class":688},[679,3553,3554],{"class":695}," PUT",[679,3556,699],{"class":695},[679,3558,703],{"class":702},[679,3560,706],{"class":695},[679,3562,710],{"class":709},[679,3564,713],{"class":702},[679,3566,3567],{"class":695},"\u002Fstudent\u002F66e6a3f5c1d2b30012a4f9c1",[679,3569,719],{"class":702},[679,3571,3572,3574,3576,3578,3580],{"class":681,"line":722},[679,3573,725],{"class":688},[679,3575,728],{"class":695},[679,3577,731],{"class":709},[679,3579,734],{"class":695},[679,3581,719],{"class":702},[679,3583,3584,3586,3588],{"class":681,"line":739},[679,3585,725],{"class":688},[679,3587,744],{"class":695},[679,3589,719],{"class":702},[679,3591,3592,3594],{"class":681,"line":749},[679,3593,752],{"class":688},[679,3595,3596],{"class":695}," '{}'\n",[669,3598,3601],{"className":758,"code":3599,"filename":3600,"language":761,"meta":675,"style":675},"{\n  \"success\": true,\n  \"message\": \"Student updated successfully.\",\n  \"data\": {\n    \"_id\": \"66e6a3f5c1d2b30012a4f9c1\",\n    \"username\": \"XXB1045\",\n    \"firstName\": \"Jane\",\n    \"lastName\": \"Doe\",\n    \"activatedPlatformsThisSeason\": [\"neo\", \"stem\"]\n  }\n}\n","Response 200 (abridged)",[402,3602,3603,3607,3617,3628,3634,3645,3657,3669,3681,3699,3703],{"__ignoreMap":675},[679,3604,3605],{"class":681,"line":682},[679,3606,768],{"class":709},[679,3608,3609,3611,3613,3615],{"class":681,"line":722},[679,3610,774],{"class":773},[679,3612,777],{"class":709},[679,3614,780],{"class":688},[679,3616,783],{"class":709},[679,3618,3619,3621,3623,3626],{"class":681,"line":739},[679,3620,788],{"class":773},[679,3622,777],{"class":709},[679,3624,3625],{"class":695},"\"Student updated successfully.\"",[679,3627,783],{"class":709},[679,3629,3630,3632],{"class":681,"line":749},[679,3631,800],{"class":773},[679,3633,803],{"class":709},[679,3635,3636,3639,3641,3643],{"class":681,"line":806},[679,3637,3638],{"class":773},"    \"_id\"",[679,3640,777],{"class":709},[679,3642,840],{"class":695},[679,3644,783],{"class":709},[679,3646,3647,3650,3652,3655],{"class":681,"line":819},[679,3648,3649],{"class":773},"    \"username\"",[679,3651,777],{"class":709},[679,3653,3654],{"class":695},"\"XXB1045\"",[679,3656,783],{"class":709},[679,3658,3659,3662,3664,3667],{"class":681,"line":832},[679,3660,3661],{"class":773},"    \"firstName\"",[679,3663,777],{"class":709},[679,3665,3666],{"class":695},"\"Jane\"",[679,3668,783],{"class":709},[679,3670,3671,3674,3676,3679],{"class":681,"line":845},[679,3672,3673],{"class":773},"    \"lastName\"",[679,3675,777],{"class":709},[679,3677,3678],{"class":695},"\"Doe\"",[679,3680,783],{"class":709},[679,3682,3683,3686,3689,3692,3694,3696],{"class":681,"line":856},[679,3684,3685],{"class":773},"    \"activatedPlatformsThisSeason\"",[679,3687,3688],{"class":709},": [",[679,3690,3691],{"class":695},"\"neo\"",[679,3693,650],{"class":709},[679,3695,827],{"class":695},[679,3697,3698],{"class":709},"]\n",[679,3700,3701],{"class":681,"line":862},[679,3702,859],{"class":709},[679,3704,3705],{"class":681,"line":1210},[679,3706,865],{"class":709},[358,3708,3709,3710,3712,3713,3715,3716,3718],{},"Without ",[402,3711,3360],{}," in the body, this route puts the organization in the path into\nthe list, keeps the entries already there, and leaves a list that holds ",[402,3714,3364],{}," as it is. Profile\nfields you include are updated too. It needs ",[402,3717,589],{}," on that organization.",[358,3720,3721,3722,3724],{},"To add several organizations at once, send them in ",[402,3723,3360],{},", on either route.\nThey are added to the student's list, and nothing already on it is removed:",[669,3726,3728],{"className":671,"code":3727,"filename":673,"language":674,"meta":675,"style":675},"curl -s -X PUT https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fstudent\u002F66e6a3f5c1d2b30012a4f9c1 \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"activatedPlatformsThisSeason\": [\"stem\", \"gmath\"] }'\n",[402,3729,3730,3745,3757,3765],{"__ignoreMap":675},[679,3731,3732,3734,3736,3738,3740,3743],{"class":681,"line":682},[679,3733,673],{"class":685},[679,3735,689],{"class":688},[679,3737,692],{"class":688},[679,3739,3554],{"class":695},[679,3741,3742],{"class":695}," https:\u002F\u002Fapi.main-team.org\u002Fv1\u002Fstudent\u002F66e6a3f5c1d2b30012a4f9c1",[679,3744,719],{"class":702},[679,3746,3747,3749,3751,3753,3755],{"class":681,"line":722},[679,3748,725],{"class":688},[679,3750,728],{"class":695},[679,3752,731],{"class":709},[679,3754,734],{"class":695},[679,3756,719],{"class":702},[679,3758,3759,3761,3763],{"class":681,"line":739},[679,3760,725],{"class":688},[679,3762,744],{"class":695},[679,3764,719],{"class":702},[679,3766,3767,3769],{"class":681,"line":749},[679,3768,752],{"class":688},[679,3770,3771],{"class":695}," '{ \"activatedPlatformsThisSeason\": [\"stem\", \"gmath\"] }'\n",[358,3773,3774,3775,582,3777,3779,3780,3782,3783,783,3786,650,3789,650,3792,3795,3796,3799,3800,3802,3803,3805,3806,470],{},"This needs ",[402,3776,589],{},[402,3778,585],{},". Either route accepts ",[402,3781,3364],{}," and the slugs ",[402,3784,3785],{},"stem",[402,3787,3788],{},"hilingua",[402,3790,3791],{},"neo",[402,3793,3794],{},"gmath"," and ",[402,3797,3798],{},"coding","; any other value, ",[402,3801,585],{}," included, is a ",[402,3804,2660],{},". No update\nremoves an organization from the list. See ",[468,3807,62],{"href":63},[370,3809,3811],{"id":3810},"what-the-student-sees","What the student sees",[476,3813,3814,3823,3829,3839],{},[479,3815,3816,3819,3820,3822],{},[362,3817,3818],{},"The panel, signed in."," The browser passes briefly through the Main Team sign-in address, then\nlands in the organization's panel as the student, on the page ",[402,3821,890],{}," named.",[479,3824,3825,3828],{},[362,3826,3827],{},"On the first visit to an organization,"," that organization creates its own copy of the student.\nFrom then on your applications, supervisor links, certificates and reports on that organization\nwork for this student.",[479,3830,3831,3834,3835,3838],{},[362,3832,3833],{},"If their email address is not confirmed,"," the student still gets in, but the panel asks them to\nconfirm the address with a 6-digit code, which it sends from ",[402,3836,3837],{},"no-reply@main-team.org",". The prompt\nappears on every page until the address is confirmed, My Exams included, so they must confirm\nbefore they can start an exam. They can't put it off; the only way out is to sign out. A student\nalready inside an exam room is not interrupted. A code is valid for 15 minutes, and they can ask\nfor a new one after 60 seconds. Tell your students to check their spam folder for the code, and to\nconfirm well before an exam day, for example right after their first sign-in.",[479,3840,3841,3844],{},[362,3842,3843],{},"If the link was already used or has expired,"," the browser shows an error saying the access token\nis invalid or expired. Nothing is wrong with the account. Pressing your button again mints a fresh\nlink.",[551,3846,3848],{"type":3847},"note",[358,3849,3850],{},"Changing a student's email address through the API withdraws its confirmation, because nobody has yet\nproved they receive mail at the new address. The next time the student uses the panel, it asks them to\nconfirm the new address and keeps asking until they do. Sending the address they already have changes\nnothing.",[370,3852,3854],{"id":3853},"test-it","Test it",[515,3856,3859,3868,3881,3888,3899],{"className":3857},[3858],"contains-task-list",[479,3860,3863,3867],{"className":3861},[3862],"task-list-item",[2285,3864],{"disabled":1141,"type":3865,"ariaLabel":3866},"checkbox","Press the button: you land in the organization's panel, signed in."," Press the button: you land in the organization's panel, signed in.",[479,3869,3871,3874,3875,3877,3878,3880],{"className":3870},[3862],[2285,3872],{"disabled":1141,"type":3865,"ariaLabel":3873},"In the browser's network tab, copy the Location of your 303 and open it in a private window. You get the invalid-or-expired error: the link was spent by the first visit."," In the browser's network tab, copy the ",[402,3876,503],{}," of your ",[402,3879,1644],{}," and open it in a private window.\nYou get the invalid-or-expired error: the link was spent by the first visit.",[479,3882,3884,3887],{"className":3883},[3862],[2285,3885],{"disabled":1141,"type":3865,"ariaLabel":3886},"Mint a link with curl, wait more than two minutes, then open it. Same error: it expired."," Mint a link with curl, wait more than two minutes, then open it. Same error: it expired.",[479,3889,3891,3894,3895,3898],{"className":3890},[3862],[2285,3892],{"disabled":1141,"type":3865,"ariaLabel":3893},"Register a test student with activatedPlatformsThisSeason: [\"neo\"] and press the stem button: your page shows the \"not entered\" message, not a raw error. An update can't take an organization away, so use a new student for this."," Register a test student with ",[402,3896,3897],{},"activatedPlatformsThisSeason: [\"neo\"]"," and press the stem\nbutton: your page shows the \"not entered\" message, not a raw error. An update can't take an\norganization away, so use a new student for this.",[479,3900,3902,3905,3906,3909],{"className":3901},[3862],[2285,3903],{"disabled":1141,"type":3865,"ariaLabel":3904},"Search your logs for accessToken=. You should find nothing."," Search your logs for ",[402,3907,3908],{},"accessToken=",". You should find nothing.",[370,3911,3913],{"id":3912},"next-steps","Next steps",[515,3915,3916,3921,3926,3931],{},[479,3917,3918,3920],{},[468,3919,80],{"href":81},": every rule for the link, in one place.",[479,3922,3923,3925],{},[468,3924,106],{"href":107},": what to do when the student wants a\ndifferent language or date.",[479,3927,3928,3930],{},[468,3929,51],{"href":52},": handling links, tokens and students' data.",[479,3932,3933,3934,783,3937,650,3940,470],{},"Reference: ",[468,3935,3936],{"href":211},"createSigninLink",[468,3938,3939],{"href":189},"updateStudent",[468,3941,3942],{"href":186},"getStudent",[3944,3945,3946],"style",{},"html pre.shiki code .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}html pre.shiki code .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}html pre.shiki code .sKwhi, html code.shiki .sKwhi{--shiki-default:#622CBC;--shiki-dark:#DBB7FF}",{"title":675,"searchDepth":722,"depth":739,"links":3948},[3949,3950,3951,3952,3955,3956,3957,3960,3961,3962],{"id":372,"depth":722,"text":373},{"id":473,"depth":722,"text":474},{"id":566,"depth":722,"text":567},{"id":666,"depth":722,"text":667,"children":3953},[3954],{"id":884,"depth":739,"text":885},{"id":1076,"depth":722,"text":1077},{"id":2169,"depth":722,"text":2170},{"id":3321,"depth":722,"text":3322,"children":3958},[3959],{"id":3515,"depth":739,"text":3516},{"id":3810,"depth":722,"text":3811},{"id":3853,"depth":722,"text":3854},{"id":3912,"depth":722,"text":3913},"Add a button to your site that signs a student into an organization's panel. Mint a single-use link on click and redirect at once, in Express and plain PHP.","md",{},[3936,3942,3967,3939,1023],"updateOrgStudent","20",{"title":353,"description":3963},"api\u002Ftutorials\u002Fsend-student-to-panel","8usxM01_UZFRFEzPsh0FU2B6dVFLcLe4Yl2jIBswliQ",[3973,3977,3982,3986,3988],{"operationId":3936,"slug":3974,"method":141,"path":3975,"tag":80,"summary":210,"deprecated":134,"public":134,"permission":3976,"scope":875,"order":1357},"create-signin-link","\u002Fv1\u002F{organizationId}\u002Fauth\u002Fsignin","auth\u002Fsignin:$org:$ID",{"operationId":3942,"slug":3978,"method":133,"path":3979,"tag":62,"summary":185,"deprecated":134,"public":134,"permission":3980,"scope":3981,"order":1247},"get-student","\u002Fv1\u002Fstudent\u002F{studentId}","student\u002Fread:$org:$ID","flat",{"operationId":3967,"slug":3983,"method":190,"path":3984,"tag":62,"summary":201,"deprecated":134,"public":134,"permission":3985,"scope":875,"order":1326},"update-org-student","\u002Fv1\u002F{organizationId}\u002Fstudent\u002F{studentId}","student\u002Fupdate:$org:$ID",{"operationId":3939,"slug":3987,"method":190,"path":3979,"tag":62,"summary":188,"deprecated":134,"public":134,"permission":3985,"scope":3981,"order":1253},"update-student",{"operationId":1023,"slug":3989,"method":133,"path":3990,"tag":89,"summary":299,"deprecated":134,"public":134,"permission":3991,"scope":875,"order":1762},"get-group-challenge-student","\u002Fv1\u002F{organizationId}\u002Fgroup-challenge\u002F{challengeId}\u002Fstudent\u002F{studentId}","group-challenge\u002Fread:$org:$ID",1791554615296]